mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 16:03:49 +00:00
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:
@@ -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()
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user