Allow selecting parent worktree when creating workspace

- Rename "Parent workspace" label to "Parent worktree"
- Filter candidates by execution host and project to prevent nesting across hosts
- Wire parentWorktreeId through composer state and creation request pipeline
- Update translations and add new copy for nesting-related messages
This commit is contained in:
Jinjing
2026-08-27 19:29:47 -07:00
parent 7933b1a203
commit af2d47d358
23 changed files with 335 additions and 41 deletions
@@ -308,6 +308,11 @@ function findRunTargetItem(label: string): HTMLElement | undefined {
let current: { container: HTMLDivElement; root: Root } | null = null
function unmountCurrent(): void {
act(() => current?.root.unmount())
current?.container.remove()
}
describe('NewWorkspaceComposerCard folder task source mode', () => {
beforeEach(() => {
;(window as unknown as { api: unknown }).api = {
@@ -335,8 +340,7 @@ describe('NewWorkspaceComposerCard folder task source mode', () => {
})
afterEach(() => {
act(() => current?.root.unmount())
current?.container.remove()
unmountCurrent()
current = null
vi.clearAllMocks()
})
@@ -397,8 +401,7 @@ describe('NewWorkspaceComposerCard folder task source mode', () => {
)
expect(collapsedReuse).toBeTruthy()
act(() => current?.root.unmount())
current?.container.remove()
unmountCurrent()
current = renderCard({ canReuseSelectedBranch: true, reuseSelectedBranch: true })
const reuseLabel = [...current.container.querySelectorAll('label')].find((label) =>
@@ -432,8 +435,7 @@ describe('NewWorkspaceComposerCard folder task source mode', () => {
clickReuseCheckbox()
expect(offChanges).toEqual([false])
act(() => current?.root.unmount())
current?.container.remove()
unmountCurrent()
// Unchecked -> checked (opting into reuse — the action that pins the branch).
const onChanges: boolean[] = []
@@ -460,8 +462,7 @@ describe('NewWorkspaceComposerCard folder task source mode', () => {
'Wait for setup to complete before starting agent'
)
act(() => current?.root.unmount())
current?.container.remove()
unmountCurrent()
current = renderCard({
advancedOpen: true,
@@ -587,12 +588,10 @@ describe('NewWorkspaceComposerCard folder task source mode', () => {
branchesEnabled: true
})
const branchInput = findInputByLabel(current.container, 'Branch name')
expect(branchInput).toBeTruthy()
const branchBlock = branchInput?.closest('div.space-y-1')
const nextField = branchBlock?.nextElementSibling
expect(nextField?.textContent).toContain('Parent workspace')
expect(nextField?.textContent).not.toContain('Note')
const nextField = findInputByLabel(current.container, 'Branch name')?.closest(
'div.space-y-1'
)?.nextElementSibling
expect(nextField?.textContent).toMatch(/Parent worktree(?!.*Note)/)
expect(nextField?.nextElementSibling?.textContent).toContain('Note')
})
@@ -903,8 +902,7 @@ describe('NewWorkspaceComposerCard note sizing', () => {
// Sizing is layout-driven (field-sizing) rather than a JS measure pass, and happy-dom
// has no layout engine, so these assert the class contract that produces the growth.
afterEach(() => {
act(() => current?.root.unmount())
current?.container.remove()
unmountCurrent()
current = null
})
@@ -57,6 +57,8 @@ export default function NewWorkspaceComposerCard(
selectedProjectId = null,
parentWorktreeId,
onParentWorktreeIdChange,
selectedRepoExecutionHostId,
selectedRepoProjectId,
activeFolderWorkspaceId,
onAddProjectOverride,
onNestedDialogOpenChange
@@ -307,6 +309,8 @@ export default function NewWorkspaceComposerCard(
showSetupAgentStartupPolicy={showSetupAgentStartupPolicy}
parentWorktreeId={parentWorktreeId}
onParentWorktreeIdChange={onParentWorktreeIdChange}
selectedRepoExecutionHostId={selectedRepoExecutionHostId}
selectedRepoProjectId={selectedRepoProjectId}
activeFolderWorkspaceId={activeFolderWorkspaceId}
/>
</div>
@@ -323,6 +323,57 @@ describe('ComposerParentWorktreePicker', () => {
expect(candidateLabels().join(' ')).not.toContain('Other repo')
})
it('excludes candidates on another execution host or project', () => {
seed([
makeWorktree({ id: 'same', displayName: 'Same host', hostId: 'local', projectId: 'proj1' }),
makeWorktree({
id: 'otherHost',
displayName: 'Other host',
hostId: 'ssh:box',
projectId: 'proj1'
}),
makeWorktree({
id: 'otherProject',
displayName: 'Other project',
hostId: 'local',
projectId: 'proj2'
})
])
render(
<ComposerParentWorktreePicker
repoId={REPO_ID}
executionHostId="local"
projectId="proj1"
value={null}
onChange={vi.fn()}
/>
)
fireEvent.click(trigger())
expect(candidateLabels().join(' ')).toContain('Same host')
expect(candidateLabels().join(' ')).not.toContain('Other host')
expect(candidateLabels().join(' ')).not.toContain('Other project')
})
// A worktree with no recorded hostId inherits its repo's host, which is the child's host too.
it('keeps candidates whose host or project is unrecorded', () => {
seed([makeWorktree({ id: 'unscoped', displayName: 'Unscoped' })])
render(
<ComposerParentWorktreePicker
repoId={REPO_ID}
executionHostId="local"
projectId="proj1"
value={null}
onChange={vi.fn()}
/>
)
fireEvent.click(trigger())
expect(candidateLabels().join(' ')).toContain('Unscoped')
})
it('restricts candidates to the active folder workspace subtree', () => {
const attached = makeWorktree({
id: 'attached',
@@ -117,7 +117,7 @@ function ComposerParentWorktreePickerImpl({
return (
<div className="space-y-1.5">
<span id={labelId} className="block text-xs font-medium text-muted-foreground">
{translate('auto.components.ComposerParentWorktreePicker.label', 'Parent workspace')}
{translate('auto.components.ComposerParentWorktreePicker.label', 'Parent worktree')}
</span>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
@@ -225,8 +225,14 @@ function ParentWorktreeCandidateList({
const [highlightedIndex, setHighlightedIndex] = useState(0)
const candidates = useMemo(() => {
const childBoundary: WorktreeLineageBoundary | null =
executionHostId && projectId ? { repoId, hostId: executionHostId, projectId } : null
// Why `?? undefined`: an unresolved host or project is "not known yet", not "no host", and
// the boundary check reads undefined on either side as a wildcard. A candidate in this repo
// with no recorded hostId inherits that repo's host, so it is on the child's host too.
const childBoundary: WorktreeLineageBoundary = {
repoId,
hostId: executionHostId ?? undefined,
projectId: projectId ?? undefined
}
const worktreeMap = getIndexedWorktreeMap(worktreesByRepo)
const cyclicLineageIds = getCyclicProjectedWorktreeLineageIds(worktreeLineageById, worktreeMap)
const folderSubtreeIds = activeFolderWorkspaceId
@@ -241,9 +247,8 @@ function ParentWorktreeCandidateList({
.filter(
(candidate) =>
candidate.repoId === repoId &&
(executionHostId === undefined || candidate.hostId === executionHostId) &&
!candidate.isArchived &&
(childBoundary === null || sharesWorktreeLineageBoundary(childBoundary, candidate)) &&
sharesWorktreeLineageBoundary(childBoundary, candidate) &&
!cyclicLineageIds.has(candidate.id) &&
(folderSubtreeIds === null || folderSubtreeIds.has(candidate.id))
)
@@ -40,6 +40,8 @@ type NewWorkspaceComposerAdvancedSectionProps = Pick<
| 'onBranchNameOverrideChange'
| 'parentWorktreeId'
| 'onParentWorktreeIdChange'
| 'selectedRepoExecutionHostId'
| 'selectedRepoProjectId'
| 'activeFolderWorkspaceId'
| 'note'
| 'onNoteChange'
@@ -80,6 +82,8 @@ export function NewWorkspaceComposerAdvancedSection({
onBranchNameOverrideChange,
parentWorktreeId = null,
onParentWorktreeIdChange,
selectedRepoExecutionHostId,
selectedRepoProjectId,
activeFolderWorkspaceId = null,
note,
onNoteChange,
@@ -201,6 +205,8 @@ export function NewWorkspaceComposerAdvancedSection({
{selectedRepoIsGit && branchesEnabled && onParentWorktreeIdChange ? (
<ComposerParentWorktreePicker
repoId={repoId}
executionHostId={selectedRepoExecutionHostId}
projectId={selectedRepoProjectId}
value={parentWorktreeId}
onChange={onParentWorktreeIdChange}
activeFolderWorkspaceId={activeFolderWorkspaceId}
@@ -18,6 +18,7 @@ import type { SparsePreset } from '../../../../shared/worktree/create-types'
import type { SshConnectionStatus } from '../../../../shared/ssh-types'
import type { TaskSourceContext } from '../../../../shared/task-source-context'
import type { TuiAgent } from '../../../../shared/tui-agent'
import type { ExecutionHostId } from '../../../../shared/execution-host'
export type RepoOption = React.ComponentProps<typeof RepoCombobox>['repos'][number]
export type EphemeralVmRecipeOption = NonNullable<OrcaHooks['environmentRecipes']>[number]
@@ -63,6 +64,8 @@ export type NewWorkspaceComposerCardProps = {
onBranchNameOverrideChange: (value: string | undefined) => void
parentWorktreeId?: string | null
onParentWorktreeIdChange?: (value: string | null) => void
selectedRepoExecutionHostId?: ExecutionHostId | null
selectedRepoProjectId?: string | null
activeFolderWorkspaceId?: string | null
onSmartGitHubItemSelect: (item: GitHubWorkItem) => void
onSmartGitLabItemSelect: (item: GitLabWorkItem) => void
@@ -14,6 +14,9 @@ export type ComposerCardSourceProps = Pick<
| 'ephemeralVmRecipeError'
| 'name'
| 'branchNameOverride'
| 'parentWorktreeId'
| 'selectedRepoExecutionHostId'
| 'selectedRepoProjectId'
| 'smartNameSelection'
| 'reuseSelectedBranch'
| 'createMultiple'
@@ -63,6 +66,7 @@ export type ComposerCardActionProps = {
smartNameRepoSwitchTarget?: 'project' | 'task-source'
onNameValueChange: ComposerModel['handleNameValueChange']
onBranchNameOverrideChange: ComposerModel['handleBranchNameOverrideChange']
onParentWorktreeIdChange: ComposerModel['setParentWorktreeId']
onSmartGitHubItemSelect: ComposerModel['handleSmartGitHubItemSelect']
onSmartGitLabItemSelect: ComposerModel['handleSmartGitLabItemSelect']
onSmartBranchSelect: ComposerModel['handleSmartBranchSelect']
@@ -13,6 +13,7 @@ export function buildComposerCardProps(state: ComposerModel) {
attachmentPaths,
baseBranch,
branchNameOverride,
parentWorktreeId,
createGateMode,
createError,
createMultiple,
@@ -81,10 +82,12 @@ export function buildComposerCardProps(state: ComposerModel) {
selectedProjectHostSetupId,
selectedProjectId,
selectedRepo,
selectedRepoExecutionHostId,
selectedRepoConnectInProgress,
selectedRepoConnectionId,
selectedRepoGitHubSourceContext,
selectedRepoIsGit,
selectedRepoProjectId,
selectedRepoRequiresConnection,
shouldWaitForIssueAutomationCheck,
sourceIntentBlocksCreate,
@@ -96,6 +99,7 @@ export function buildComposerCardProps(state: ComposerModel) {
setCreateMultiple,
setLinkQuery,
setNote,
setParentWorktreeId,
setSelectedEphemeralVmRecipeId,
setSetupDecision,
setSmartNameMode,
@@ -157,6 +161,10 @@ export function buildComposerCardProps(state: ComposerModel) {
onNameValueChange: handleNameValueChange,
branchNameOverride: isProjectGroupTarget ? undefined : branchNameOverride,
onBranchNameOverrideChange: isProjectGroupTarget ? () => {} : handleBranchNameOverrideChange,
parentWorktreeId: isProjectGroupTarget ? null : parentWorktreeId,
onParentWorktreeIdChange: isProjectGroupTarget ? () => {} : setParentWorktreeId,
selectedRepoExecutionHostId: isProjectGroupTarget ? null : selectedRepoExecutionHostId,
selectedRepoProjectId: isProjectGroupTarget ? null : selectedRepoProjectId,
onSmartGitHubItemSelect: handleSmartGitHubItemSelect,
onSmartGitLabItemSelect: handleSmartGitLabItemSelect,
onSmartBranchSelect: isProjectGroupTarget ? () => {} : handleSmartBranchSelect,
@@ -55,6 +55,7 @@ export type ComposerStoreActions = {
linkedTaskSourceContext?: TaskSourceContext | null
startupDraft?: string
nameWasGenerated?: boolean
parentWorktreeId?: string
provisionedRoot?: {
runtimeId: string
executionHostId: ExecutionHostId
@@ -98,6 +98,7 @@ export function useComposerSubmitOrchestration(
normalizedSparseDirectories: target.derivedComposerState.normalizedSparseDirectories,
note: target.sourceContextState.note,
onCreated: target.composerTargetStore.onCreated,
parentWorktreeId: target.workspaceIdentityState.parentWorktreeId,
persistDraft: target.composerTargetStore.persistDraft,
persistSetupAgentStartupPolicy: target.providerRuntimeSync.persistSetupAgentStartupPolicy,
prepareFullSubmit: fullSubmitPreparation.prepareFullSubmit,
@@ -210,6 +211,7 @@ export function useComposerSubmitOrchestration(
linkedGitLabMR: target.workspaceIdentityState.linkedGitLabMR,
normalizedSparseDirectories: target.derivedComposerState.normalizedSparseDirectories,
onCreated: target.composerTargetStore.onCreated,
parentWorktreeId: target.workspaceIdentityState.parentWorktreeId,
persistDraft: target.composerTargetStore.persistDraft,
persistSetupAgentStartupPolicy: target.providerRuntimeSync.persistSetupAgentStartupPolicy,
prepareQuickSubmit: quickSubmitPreparation.prepareQuickSubmit,
@@ -72,6 +72,7 @@ describe('useFullCreationExecution cancellation', () => {
normalizedSparseDirectories: [],
note: '',
onCreated: vi.fn<NonNullable<FullCreationExecutionInput['onCreated']>>(),
parentWorktreeId: null,
persistDraft: false,
persistSetupAgentStartupPolicy,
prepareFullSubmit: vi
@@ -12,6 +12,7 @@ export type FullCreationExecutionInput = Pick<
| 'normalizedSparseDirectories'
| 'note'
| 'onCreated'
| 'parentWorktreeId'
| 'persistDraft'
| 'persistSetupAgentStartupPolicy'
| 'prepareFullSubmit'
@@ -47,6 +48,7 @@ export function useFullCreationExecution(input: FullCreationExecutionInput) {
normalizedSparseDirectories,
note,
onCreated,
parentWorktreeId,
persistDraft,
persistSetupAgentStartupPolicy,
prepareFullSubmit,
@@ -155,7 +157,8 @@ export function useFullCreationExecution(input: FullCreationExecutionInput) {
nameWasGenerated,
...(!backendStartup && startupPlan?.draftPrompt
? { startupDraft: startupPlan.draftPrompt }
: {})
: {}),
...(parentWorktreeId ? { parentWorktreeId } : {})
}
)
@@ -251,6 +254,7 @@ export function useFullCreationExecution(input: FullCreationExecutionInput) {
normalizedSparseDirectories,
note,
onCreated,
parentWorktreeId,
persistDraft,
persistSetupAgentStartupPolicy,
prepareFullSubmit,
@@ -18,6 +18,8 @@ export type ComposerIdentityModel = {
setCompareBaseRef: React.Dispatch<React.SetStateAction<string | undefined>>
branchNameOverride: string | undefined
setBranchNameOverride: React.Dispatch<React.SetStateAction<string | undefined>>
parentWorktreeId: string | null
setParentWorktreeId: React.Dispatch<React.SetStateAction<string | null>>
branchNameOverridePreservesNameEdits: boolean
setBranchNameOverridePreservesNameEdits: React.Dispatch<React.SetStateAction<boolean>>
smartNameMode: SmartNameMode
@@ -12,6 +12,7 @@ type QuickCreationExecutionInput = Pick<
| 'linkedGitLabMR'
| 'normalizedSparseDirectories'
| 'onCreated'
| 'parentWorktreeId'
| 'persistDraft'
| 'persistSetupAgentStartupPolicy'
| 'prepareQuickSubmit'
@@ -58,6 +59,7 @@ export function useQuickCreationExecution(input: QuickCreationExecutionInput) {
linkedGitLabMR,
normalizedSparseDirectories,
onCreated,
parentWorktreeId,
persistDraft,
persistSetupAgentStartupPolicy,
prepareQuickSubmit,
@@ -217,6 +219,7 @@ export function useQuickCreationExecution(input: QuickCreationExecutionInput) {
linkedLinearIssueWorkspaceId,
linkedLinearIssueOrganizationUrlKey,
branchNameOverride: effectiveBranchNameOverride,
parentWorktreeId,
workspaceStatus: resolvedInitialWorkspaceStatus,
linkedGitLabMR,
linkedGitLabIssue,
@@ -259,6 +262,7 @@ export function useQuickCreationExecution(input: QuickCreationExecutionInput) {
linkedGitLabMR,
normalizedSparseDirectories,
onCreated,
parentWorktreeId,
persistDraft,
persistSetupAgentStartupPolicy,
prepareQuickSubmit,
@@ -29,6 +29,7 @@ function createInput(
linkedLinearIssueWorkspaceId: undefined,
linkedLinearIssueOrganizationUrlKey: undefined,
branchNameOverride: undefined,
parentWorktreeId: null,
workspaceStatus: undefined,
linkedGitLabMR: null,
linkedGitLabIssue: null,
@@ -87,6 +88,7 @@ describe('quick composer creation request', () => {
linkedLinearIssueWorkspaceId: 'workspace-1',
linkedLinearIssueOrganizationUrlKey: 'orca',
branchNameOverride: 'feature',
parentWorktreeId: 'repo-1::/worktrees/parent',
workspaceStatus: 'in-progress',
linkedGitLabMR: 9,
linkedGitLabIssue: 8,
@@ -107,6 +109,7 @@ describe('quick composer creation request', () => {
linkedGitLabMR: 9,
linkedGitLabIssue: 8,
branchNameOverride: 'feature',
parentWorktreeId: 'repo-1::/worktrees/parent',
launchDraftPrompt: 'draft',
suppressTerminalFocusOnCompletion: true
})
@@ -33,6 +33,7 @@ export type QuickCreationRequestInput = {
linkedLinearIssueWorkspaceId: string | undefined
linkedLinearIssueOrganizationUrlKey: string | undefined
branchNameOverride: string | undefined
parentWorktreeId: string | null
workspaceStatus: WorktreeCreationRequest['workspaceStatus']
linkedGitLabMR: number | null
linkedGitLabIssue: number | null
@@ -88,6 +89,7 @@ export function buildQuickCreationRequest(
? { linkedLinearIssueOrganizationUrlKey: input.linkedLinearIssueOrganizationUrlKey }
: {}),
...(input.branchNameOverride ? { branchNameOverride: input.branchNameOverride } : {}),
...(input.parentWorktreeId ? { parentWorktreeId: input.parentWorktreeId } : {}),
...(input.workspaceStatus ? { workspaceStatus: input.workspaceStatus } : {}),
...(input.includeGitLabLinks && input.linkedGitLabMR != null
? { linkedGitLabMR: input.linkedGitLabMR }
@@ -100,6 +100,8 @@ export function useWorkspaceIdentityState(input: WorkspaceIdentityStateInput) {
initialLinearBranchName
)
const [parentWorktreeId, setParentWorktreeId] = useState<string | null>(null)
const [branchNameOverridePreservesNameEdits, setBranchNameOverridePreservesNameEdits] = useState(
Boolean(initialLinearBranchName)
)
@@ -195,6 +197,8 @@ export function useWorkspaceIdentityState(input: WorkspaceIdentityStateInput) {
setCompareBaseRef,
branchNameOverride,
setBranchNameOverride,
parentWorktreeId,
setParentWorktreeId,
branchNameOverridePreservesNameEdits,
setBranchNameOverridePreservesNameEdits,
smartNameMode,
@@ -1,6 +1,6 @@
// @vitest-environment happy-dom
import { renderHook } from '@testing-library/react'
import { act, renderHook } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useAppStore } from '@/store'
import { useComposerState } from './useComposerState'
@@ -31,7 +31,7 @@ afterEach(() => {
})
describe('useComposerState integrated lifecycle', () => {
it('composes two live composers and releases each native drop listener on unmount', () => {
it('composes two live composers and exposes the parent-worktree control state', () => {
useAppStore.setState({
repos: [],
projects: [],
@@ -57,6 +57,10 @@ describe('useComposerState integrated lifecycle', () => {
expect(first.result.current.cardProps.name).toBe('first')
expect(second.result.current.cardProps.name).toBe('second')
expect(first.result.current.cardProps.parentWorktreeId).toBeNull()
expect(first.result.current.cardProps.onParentWorktreeIdChange).toBeTypeOf('function')
act(() => first.result.current.cardProps.onParentWorktreeIdChange('repo-1::/parent'))
expect(first.result.current.cardProps.parentWorktreeId).toBe('repo-1::/parent')
expect(window.api.ui.onFileDrop).toHaveBeenCalledTimes(2)
second.unmount()
+11 -1
View File
@@ -448,7 +448,10 @@
}
}
}
}
},
"createdWithoutParentNesting": "Created without nesting under \"{{value0}}\"",
"createdWithoutParentNestingUnnamed": "Created without nesting under the selected parent",
"createdWithoutParentNestingDetail": "The parent workspace was no longer available. You can set it from the workspace menu."
},
"repos": {
"2975400634": "Update Orca server to open non-Git folders on this runtime.",
@@ -16293,6 +16296,13 @@
"remoteLabel": "Browsers on {{value0}}",
"clientNoteLocalStorage": "Imports read this device’s browsers. Cookies are stored locally.",
"remoteNoteRemoteStorage": "Imports read browsers on {{value0}}. Cookies are stored on that machine, and a permission prompt may appear on its screen."
},
"ComposerParentWorktreePicker": {
"label": "Parent worktree",
"noParent": "No parent",
"description": "Nests this workspace under another in the sidebar. Does not change the base branch.",
"searchPlaceholder": "Search workspaces...",
"noMatches": "No matches."
}
},
"i18n": {
+11 -1
View File
@@ -263,7 +263,10 @@
"3b57982bf6": "Git no pudo eliminar de forma segura la rama \"{{value0}}\" después de eliminar el espacio de trabajo \"{{value1}}\", así que Orca la conservó para evitar perder commits locales.",
"81f13f48d2": "Git no pudo eliminar de forma segura la rama \"{{value0}}\" después de eliminar el worktree \"{{value1}}\", así que Orca la conservó para evitar perder commits locales.",
"runtimeScopeForbiddenTitle": "Esta conexión tiene acceso limitado (móvil)",
"runtimeScopeForbiddenDescription": "Los espacios de trabajo no están disponibles con un emparejamiento de acceso móvil. Vuelve a conectarte con el enlace del navegador desde Ajustes → Servidores remotos de Orca → Comparte este servidor Orca."
"runtimeScopeForbiddenDescription": "Los espacios de trabajo no están disponibles con un emparejamiento de acceso móvil. Vuelve a conectarte con el enlace del navegador desde Ajustes → Servidores remotos de Orca → Comparte este servidor Orca.",
"createdWithoutParentNesting": "Creado sin anidarlo bajo \"{{value0}}\"",
"createdWithoutParentNestingUnnamed": "Creado sin anidarlo bajo el worktree padre seleccionado",
"createdWithoutParentNestingDetail": "El espacio de trabajo padre ya no estaba disponible. Puedes establecerlo desde el menú del espacio de trabajo."
},
"repos": {
"2975400634": "Actualiza el servidor Orca para abrir carpetas que no sean Git en este host.",
@@ -14434,6 +14437,13 @@
"deleteDescription": "“{{name}}” ya no estará disponible en su enlace público.",
"delete": "Eliminar"
}
},
"ComposerParentWorktreePicker": {
"label": "Worktree padre",
"noParent": "Sin worktree padre",
"description": "Anida este espacio de trabajo bajo otro en la barra lateral. No cambia la rama base.",
"searchPlaceholder": "Buscar espacios de trabajo...",
"noMatches": "No hay coincidencias."
}
},
"i18n": {
+11 -1
View File
@@ -263,7 +263,10 @@
"3b57982bf6": "ワークスペース\"{{value1}}\"を削除した後、Git はブランチ\"{{value0}}\"を安全に削除できなかったため、Orca はローカルコミットを失わないように保持しました。",
"81f13f48d2": "ワークツリー\"{{value1}}\"を削除した後、Git はブランチ\"{{value0}}\"を安全に削除できなかったため、Orca はローカルコミットを失わないように保持しました。",
"runtimeScopeForbiddenTitle": "この接続は制限付き(モバイル)アクセスです",
"runtimeScopeForbiddenDescription": "モバイル向けペアリングではワークスペースを使用できません。[設定] → [リモート Orca サーバー] → [この Orca サーバーを共有する] から、ブラウザ用リンクで再接続してください。"
"runtimeScopeForbiddenDescription": "モバイル向けペアリングではワークスペースを使用できません。[設定] → [リモート Orca サーバー] → [この Orca サーバーを共有する] から、ブラウザ用リンクで再接続してください。",
"createdWithoutParentNesting": "「{{value0}}」の配下にネストせずに作成しました",
"createdWithoutParentNestingUnnamed": "選択した親ワークツリーの配下にネストせずに作成しました",
"createdWithoutParentNestingDetail": "親ワークスペースは利用できなくなりました。ワークスペースメニューから設定できます。"
},
"repos": {
"2975400634": "Orca サーバーを更新して、このランタイムで Git 以外のフォルダーを開くようにします。",
@@ -14425,6 +14428,13 @@
"deleteDescription": "「{{name}}」は公開リンクで利用できなくなります。",
"delete": "削除"
}
},
"ComposerParentWorktreePicker": {
"label": "親ワークツリー",
"noParent": "親なし",
"description": "このワークスペースをサイドバー内の別のワークスペースの配下にネストします。ベースブランチは変更されません。",
"searchPlaceholder": "ワークスペースを検索...",
"noMatches": "一致する項目がありません。"
}
},
"i18n": {
+82 -7
View File
@@ -266,7 +266,10 @@
"3b57982bf6": "\"{{value1}}\" 워크스페이스를 삭제한 후 Git이 \"{{value0}}\" 브랜치를 안전하게 삭제할 수 없어 Orca가 로컬 커밋 손실을 방지하기 위해 유지했습니다.",
"81f13f48d2": "\"{{value1}}\" 워크트리를 삭제한 후 Git이 \"{{value0}}\" 브랜치를 안전하게 삭제할 수 없어 Orca가 로컬 커밋 손실을 방지하기 위해 유지했습니다.",
"runtimeScopeForbiddenTitle": "이 연결은 제한된(모바일) 액세스입니다",
"runtimeScopeForbiddenDescription": "모바일 범위 페어링에서는 워크스페이스를 사용할 수 없습니다. 설정 → 원격 Orca 서버 → 이 Orca 서버 공유에서 브라우저 액세스 링크로 다시 연결하세요."
"runtimeScopeForbiddenDescription": "모바일 범위 페어링에서는 워크스페이스를 사용할 수 없습니다. 설정 → 원격 Orca 서버 → 이 Orca 서버 공유에서 브라우저 액세스 링크로 다시 연결하세요.",
"createdWithoutParentNesting": "\"{{value0}}\" 아래에 중첩하지 않고 생성했습니다",
"createdWithoutParentNestingUnnamed": "선택한 상위 워크트리 아래에 중첩하지 않고 생성했습니다",
"createdWithoutParentNestingDetail": "상위 워크스페이스를 더 이상 사용할 수 없습니다. 워크스페이스 메뉴에서 설정할 수 있습니다."
},
"repos": {
"2975400634": "이 런타임에서 Git이 아닌 폴더를 열려면 Orca 서버를 업데이트하세요.",
@@ -3879,17 +3882,82 @@
},
"skills": {
"SkillsPage": {
"cb142070b4": "새로 고침", "a68dee6a32": "스킬 검색", "f43ad6edf3": "스킬", "ea72d6185b": "스킬을 검색하지 못했습니다", "dc4c3328ee": "파일 표시", "9963dff6d3": "설명을 찾을 수 없습니다.", "995fde8337": "스킬 파일을 표시하지 못했습니다", "4acd6d68ec": "스킬을 찾을 수 없습니다", "6a62a0168c": "일치하는 항목 없음", "08a321a984": "현재 검색 및 필터와 일치하는 스킬이 없습니다.", "cd7893fbc1": "스킬 검색 중", "35b9a724a0": "사용 가능", "c13b82793c": "설치 관리", "aee7b99cc6": "링크에서 설치", "filterProvider": "에이전트별 필터", "filterSource": "소스별 필터", "allSources": "전체", "clearFilters": "필터 지우기", "closeSkills": "스킬 닫기", "closeTooltip": "닫기 · Esc", "moreActions": "추가 작업", "sharedLinks": "공유 링크", "emptyCopy": "검색한 스킬 폴더가 비어 있습니다. 공유 번들을 설치하거나 스킬을 추가한 후 새로 고치세요.", "retry": "다시 시도", "remoteShareNotice": "이 스킬은 {{host}}에 있습니다. 공유하려면 해당 머신에서 스킬을 여세요.", "viewSwitch": "표시", "searchLinks": "링크 검색", "deleteSkills": "스킬 삭제…"
"cb142070b4": "새로 고침",
"a68dee6a32": "스킬 검색",
"f43ad6edf3": "스킬",
"ea72d6185b": "스킬을 검색하지 못했습니다",
"dc4c3328ee": "파일 표시",
"9963dff6d3": "설명을 찾을 수 없습니다.",
"995fde8337": "스킬 파일을 표시하지 못했습니다",
"4acd6d68ec": "스킬을 찾을 수 없습니다",
"6a62a0168c": "일치하는 항목 없음",
"08a321a984": "현재 검색 및 필터와 일치하는 스킬이 없습니다.",
"cd7893fbc1": "스킬 검색 중",
"35b9a724a0": "사용 가능",
"c13b82793c": "설치 관리",
"aee7b99cc6": "링크에서 설치",
"filterProvider": "에이전트별 필터",
"filterSource": "소스별 필터",
"allSources": "전체",
"clearFilters": "필터 지우기",
"closeSkills": "스킬 닫기",
"closeTooltip": "닫기 · Esc",
"moreActions": "추가 작업",
"sharedLinks": "공유 링크",
"emptyCopy": "검색한 스킬 폴더가 비어 있습니다. 공유 번들을 설치하거나 스킬을 추가한 후 새로 고치세요.",
"retry": "다시 시도",
"remoteShareNotice": "이 스킬은 {{host}}에 있습니다. 공유하려면 해당 머신에서 스킬을 여세요.",
"viewSwitch": "표시",
"searchLinks": "링크 검색",
"deleteSkills": "스킬 삭제…"
},
"SkillShareSelectionControls": { "01c5a15e02": "스킬 공유" },
"SkillRow": { "updatedUnknown": "날짜 없음", "pathCopied": "경로 복사됨", "copyPath": "경로 복사", "detailPath": "경로", "skillActions": "{{value0}} 작업", "viewDetails": "세부 정보 보기", "deleteSkill": "삭제…" },
"SkillRow": {
"updatedUnknown": "날짜 없음",
"pathCopied": "경로 복사됨",
"copyPath": "경로 복사",
"detailPath": "경로",
"skillActions": "{{value0}} 작업",
"viewDetails": "세부 정보 보기",
"deleteSkill": "삭제…"
},
"SkillsList": { "listLabel": "스킬" },
"sourceStatus": { "missing": "폴더를 찾을 수 없음", "remoteRepo": "원격 리포지토리 — 검색 안 됨", "unavailable": "검색 안 됨" },
"sourceStatus": {
"missing": "폴더를 찾을 수 없음",
"remoteRepo": "원격 리포지토리 — 검색 안 됨",
"unavailable": "검색 안 됨"
},
"sources": { "heading": "스킬 폴더" },
"sourceKind": { "home": "홈", "workspace": "워크스페이스", "bundled": "번들", "plugin": "플러그인" },
"count": { "skillOne": "스킬 {{count}}개", "skillOther": "스킬 {{count}}개", "sourceOne": "소스 {{count}}개", "sourceOther": "소스 {{count}}개", "fileOne": "파일 {{count}}개", "fileOther": "파일 {{count}}개", "resultOne": "결과 {{count}}개", "resultOther": "결과 {{count}}개", "selected": "{{count}}개 선택됨", "shareOne": "스킬 {{count}}개 공유", "shareOther": "스킬 {{count}}개 공유", "linkOne": "링크 {{count}}개", "linkOther": "링크 {{count}}개" },
"sourceKind": {
"home": "홈",
"workspace": "워크스페이스",
"bundled": "번들",
"plugin": "플러그인"
},
"count": {
"skillOne": "스킬 {{count}}개",
"skillOther": "스킬 {{count}}개",
"sourceOne": "소스 {{count}}개",
"sourceOther": "소스 {{count}}개",
"fileOne": "파일 {{count}}개",
"fileOther": "파일 {{count}}개",
"resultOne": "결과 {{count}}개",
"resultOther": "결과 {{count}}개",
"selected": "{{count}}개 선택됨",
"shareOne": "스킬 {{count}}개 공유",
"shareOther": "스킬 {{count}}개 공유",
"linkOne": "링크 {{count}}개",
"linkOther": "링크 {{count}}개"
},
"filter": { "allAgents": "모든 에이전트", "sharedAgent": "공유됨 (.agents)" },
"SkillsSelectionHeader": { "exit": "선택 나가기", "exitTooltip": "선택 나가기 · Esc", "title": "공유할 스킬 선택", "selectAll": "가능한 {{count}}개 모두 선택", "clear": "지우기", "deleteTitle": "삭제할 스킬 선택" },
"SkillsSelectionHeader": {
"exit": "선택 나가기",
"exitTooltip": "선택 나가기 · Esc",
"title": "공유할 스킬 선택",
"selectAll": "가능한 {{count}}개 모두 선택",
"clear": "지우기",
"deleteTitle": "삭제할 스킬 선택"
},
"SkillDetailDialog": { "agents": "에이전트", "updated": "업데이트됨", "copy": "복사" },
"SkillFreshnessNudge": {
"titleOne": "설치된 Orca 스킬이 오래되었습니다",
@@ -14392,6 +14460,13 @@
"deleteDescription": "“{{name}}”은(는) 공개 링크에서 더 이상 사용할 수 없습니다.",
"delete": "삭제"
}
},
"ComposerParentWorktreePicker": {
"label": "상위 워크트리",
"noParent": "상위 워크트리 없음",
"description": "이 워크스페이스를 사이드바의 다른 워크스페이스 아래에 중첩합니다. 기준 브랜치는 변경되지 않습니다.",
"searchPlaceholder": "워크스페이스 검색...",
"noMatches": "일치하는 항목이 없습니다."
}
},
"i18n": {
+90 -7
View File
@@ -266,7 +266,10 @@
"3b57982bf6": "删除工作区\"{{value1}}\"后,Git 无法安全删除分支\"{{value0}}\",因此 Orca 将其保留以避免丢失本地提交。",
"81f13f48d2": "删除工作树\"{{value1}}\"后,Git 无法安全删除分支\"{{value0}}\",因此 Orca 将其保留以避免丢失本地提交。",
"runtimeScopeForbiddenTitle": "此连接具有受限(移动端)访问权限",
"runtimeScopeForbiddenDescription": "移动端范围的配对无法使用工作区。请通过设置 → 远程 Orca 服务器 → 分享此 Orca 服务器中的浏览器访问链接重新连接。"
"runtimeScopeForbiddenDescription": "移动端范围的配对无法使用工作区。请通过设置 → 远程 Orca 服务器 → 分享此 Orca 服务器中的浏览器访问链接重新连接。",
"createdWithoutParentNesting": "已创建,但未嵌套在“{{value0}}”下",
"createdWithoutParentNestingUnnamed": "已创建,但未嵌套在所选父工作树下",
"createdWithoutParentNestingDetail": "父工作区已不可用。您可以从工作区菜单中重新设置。"
},
"repos": {
"2975400634": "请更新 Orca 服务器,以便在此运行时打开非 Git 文件夹。",
@@ -3889,17 +3892,90 @@
},
"skills": {
"SkillsPage": {
"cb142070b4": "刷新", "a68dee6a32": "搜索技能", "f43ad6edf3": "技能", "ea72d6185b": "无法扫描技能", "dc4c3328ee": "显示文件", "9963dff6d3": "未找到说明。", "995fde8337": "无法显示技能文件", "4acd6d68ec": "未找到技能", "6a62a0168c": "无匹配项", "08a321a984": "没有技能匹配当前搜索和筛选条件。", "cd7893fbc1": "正在扫描技能", "35b9a724a0": "可用", "c13b82793c": "管理安装", "aee7b99cc6": "从链接安装", "filterProvider": "按 Agent 筛选", "filterSource": "按来源筛选", "allSources": "全部", "clearFilters": "清除筛选", "closeSkills": "关闭技能", "closeTooltip": "关闭 · Esc", "moreActions": "更多操作", "sharedLinks": "共享链接", "emptyCopy": "扫描的技能文件夹为空。请安装共享包,或添加技能后刷新。", "retry": "重试", "remoteShareNotice": "这些技能位于 {{host}}。请在该机器上打开“技能”以进行共享。", "viewSwitch": "显示", "searchLinks": "搜索链接", "deleteSkills": "删除技能…"
"cb142070b4": "刷新",
"a68dee6a32": "搜索技能",
"f43ad6edf3": "技能",
"ea72d6185b": "无法扫描技能",
"dc4c3328ee": "显示文件",
"9963dff6d3": "未找到说明。",
"995fde8337": "无法显示技能文件",
"4acd6d68ec": "未找到技能",
"6a62a0168c": "无匹配项",
"08a321a984": "没有技能匹配当前搜索和筛选条件。",
"cd7893fbc1": "正在扫描技能",
"35b9a724a0": "可用",
"c13b82793c": "管理安装",
"aee7b99cc6": "从链接安装",
"filterProvider": "按 Agent 筛选",
"filterSource": "按来源筛选",
"allSources": "全部",
"clearFilters": "清除筛选",
"closeSkills": "关闭技能",
"closeTooltip": "关闭 · Esc",
"moreActions": "更多操作",
"sharedLinks": "共享链接",
"emptyCopy": "扫描的技能文件夹为空。请安装共享包,或添加技能后刷新。",
"retry": "重试",
"remoteShareNotice": "这些技能位于 {{host}}。请在该机器上打开“技能”以进行共享。",
"viewSwitch": "显示",
"searchLinks": "搜索链接",
"deleteSkills": "删除技能…"
},
"SkillShareSelectionControls": { "01c5a15e02": "共享技能" },
"SkillRow": { "updatedUnknown": "无日期", "pathCopied": "路径已复制", "copyPath": "复制路径", "detailPath": "路径", "skillActions": "{{value0}} 的操作", "viewDetails": "查看详情", "deleteSkill": "删除…" },
"SkillRow": {
"updatedUnknown": "无日期",
"pathCopied": "路径已复制",
"copyPath": "复制路径",
"detailPath": "路径",
"skillActions": "{{value0}} 的操作",
"viewDetails": "查看详情",
"deleteSkill": "删除…"
},
"SkillsList": { "listLabel": "技能" },
"sourceStatus": { "missing": "未找到文件夹", "remoteRepo": "远程仓库 — 未扫描", "unavailable": "未扫描" },
"sourceStatus": {
"missing": "未找到文件夹",
"remoteRepo": "远程仓库 — 未扫描",
"unavailable": "未扫描"
},
"sources": { "heading": "技能文件夹" },
"sourceKind": { "home": "主目录", "workspace": "工作区", "bundled": "内置", "plugin": "插件" },
"count": { "skillOne": "{{count}} 个技能", "skillOther": "{{count}} 个技能", "sourceOne": "{{count}} 个来源", "sourceOther": "{{count}} 个来源", "fileOne": "{{count}} 个文件", "fileOther": "{{count}} 个文件", "resultOne": "{{count}} 个结果", "resultOther": "{{count}} 个结果", "selected": "已选择 {{count}} 个", "shareOne": "共享 {{count}} 个技能", "shareOther": "共享 {{count}} 个技能", "linkOne": "{{count}} 个链接", "linkOther": "{{count}} 个链接", "deleteOne": "删除 {{count}} 个技能", "deleteOther": "删除 {{count}} 个技能", "deletedOne": "已删除 {{count}} 个技能", "deletedOther": "已删除 {{count}} 个技能", "deleteFolderOne": "{{count}} 个文件夹", "deleteFolderOther": "{{count}} 个文件夹", "deleteLinkOne": "{{count}} 个链接", "deleteLinkOther": "{{count}} 个链接" },
"sourceKind": {
"home": "主目录",
"workspace": "工作区",
"bundled": "内置",
"plugin": "插件"
},
"count": {
"skillOne": "{{count}} 个技能",
"skillOther": "{{count}} 个技能",
"sourceOne": "{{count}} 个来源",
"sourceOther": "{{count}} 个来源",
"fileOne": "{{count}} 个文件",
"fileOther": "{{count}} 个文件",
"resultOne": "{{count}} 个结果",
"resultOther": "{{count}} 个结果",
"selected": "已选择 {{count}} 个",
"shareOne": "共享 {{count}} 个技能",
"shareOther": "共享 {{count}} 个技能",
"linkOne": "{{count}} 个链接",
"linkOther": "{{count}} 个链接",
"deleteOne": "删除 {{count}} 个技能",
"deleteOther": "删除 {{count}} 个技能",
"deletedOne": "已删除 {{count}} 个技能",
"deletedOther": "已删除 {{count}} 个技能",
"deleteFolderOne": "{{count}} 个文件夹",
"deleteFolderOther": "{{count}} 个文件夹",
"deleteLinkOne": "{{count}} 个链接",
"deleteLinkOther": "{{count}} 个链接"
},
"filter": { "allAgents": "所有 Agent", "sharedAgent": "共享 (.agents)" },
"SkillsSelectionHeader": { "exit": "退出选择", "exitTooltip": "退出选择 · Esc", "title": "选择要共享的技能", "selectAll": "选择全部 {{count}} 个符合条件的项目", "clear": "清除", "deleteTitle": "选择要删除的技能" },
"SkillsSelectionHeader": {
"exit": "退出选择",
"exitTooltip": "退出选择 · Esc",
"title": "选择要共享的技能",
"selectAll": "选择全部 {{count}} 个符合条件的项目",
"clear": "清除",
"deleteTitle": "选择要删除的技能"
},
"SkillDetailDialog": { "agents": "Agent", "updated": "已更新", "copy": "复制" },
"SkillFreshnessNudge": {
"titleOne": "已安装的 Orca 技能已过期",
@@ -14408,6 +14484,13 @@
"deleteDescription": "“{{name}}” 将无法再通过其公共链接访问。",
"delete": "删除"
}
},
"ComposerParentWorktreePicker": {
"label": "父工作树",
"noParent": "无父工作树",
"description": "在侧边栏中将此工作区嵌套到另一个工作区下。不会更改基础分支。",
"searchPlaceholder": "搜索工作区...",
"noMatches": "无匹配项。"
}
},
"i18n": {