mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(composer): retain the imported project group target (#9551)
* fix(composer): keep the imported group as the target after a grouped nested import Grouped folder imports create a ProjectGroup, but the hosted Add Project handoff selected only the first imported repo, dropping the group the user explicitly chose. Carry the root group id through the completion path and select the group's folder-workspace target in the composer, falling back to the single-project handoff when no group was created or it is no longer selectable. Sidebar default-checkout and separate imports are unchanged. Part A of #9529. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(sidebar): document the CompleteGitRepoAdd handoff contract Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: just-zzp <264744947+just-zzp@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
This commit is contained in:
co-authored by
Claude Fable 5
just-zzp
Neil
parent
b46be798ed
commit
2b33fed6a5
@@ -139,7 +139,8 @@ function QuickTabBody({
|
||||
nameInputRef,
|
||||
submitQuick,
|
||||
createDisabled: formDisabled,
|
||||
selectAddedProjectRepo
|
||||
selectAddedProjectRepo,
|
||||
selectAddedProjectGroup
|
||||
} = useComposerState({
|
||||
initialName: modalData.prefilledName ?? '',
|
||||
// Why: the modal is quick-create only now, so prompt-prefill state is
|
||||
@@ -283,9 +284,10 @@ function QuickTabBody({
|
||||
open: addProjectOpen,
|
||||
onOpenChange: setAddProjectOpen,
|
||||
onProjectAdded: handleProjectAdded,
|
||||
onProjectGroupImported: selectAddedProjectGroup,
|
||||
onCloseAutoFocus: handleAddProjectCloseAutoFocus
|
||||
}),
|
||||
[addProjectOpen, handleAddProjectCloseAutoFocus, handleProjectAdded]
|
||||
[addProjectOpen, handleAddProjectCloseAutoFocus, handleProjectAdded, selectAddedProjectGroup]
|
||||
)
|
||||
const selectedProjectOption = cardProps.projectOptions.find(
|
||||
(option) => option.id === cardProps.selectedProjectId
|
||||
|
||||
@@ -83,11 +83,29 @@ describe('useAddRepoHostedController', () => {
|
||||
onOpenChange,
|
||||
onProjectAdded
|
||||
})
|
||||
await finishProjectAdd?.('repo-1')
|
||||
await finishProjectAdd?.('repo-1', 'local_folder_picker')
|
||||
expect(mocks.markOnboardingProjectAdded).toHaveBeenCalledWith('addedRepo')
|
||||
expect(onProjectAdded).toHaveBeenCalledWith('repo-1')
|
||||
// Why: closing before selection keeps the composer visible under the
|
||||
// dialog's close animation while the new project lands in the picker.
|
||||
expect(order).toEqual(['close', 'added'])
|
||||
})
|
||||
it.each([true, false])(
|
||||
'prefers the imported group when host accepts it (%s)',
|
||||
async (accepted) => {
|
||||
const onProjectAdded = vi.fn()
|
||||
const onProjectGroupImported = vi.fn(() => accepted)
|
||||
const { finishProjectAdd } = useAddRepoHostedController({
|
||||
open: true,
|
||||
onOpenChange: vi.fn(),
|
||||
onProjectAdded,
|
||||
onProjectGroupImported
|
||||
})
|
||||
await finishProjectAdd?.('repo-1', 'ssh_remote_path', 'ssh:box', {
|
||||
importedProjectGroupId: 'group'
|
||||
})
|
||||
expect(onProjectGroupImported).toHaveBeenCalledWith('group')
|
||||
expect(onProjectAdded).toHaveBeenCalledTimes(accepted ? 0 : 1)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { CompleteGitRepoAdd } from './use-complete-git-repo-add'
|
||||
import { useMemo } from 'react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { markOnboardingProjectAdded } from '@/lib/onboarding-project-checklist'
|
||||
@@ -10,6 +11,7 @@ export type AddRepoDialogHostedController = {
|
||||
/** Called after a Git project is added; the host selects the new project
|
||||
* instead of running the default-checkout navigation handoff. */
|
||||
onProjectAdded: (repoId: string) => void | Promise<void>
|
||||
onProjectGroupImported?: (groupId: string) => boolean | Promise<boolean>
|
||||
/** Radix close-autofocus hook for the nested dialog. Lets the host redirect
|
||||
* focus (e.g. to the composer's name field) instead of Radix restoring it
|
||||
* to the already-unmounted combobox row that opened the dialog. */
|
||||
@@ -23,27 +25,34 @@ export function useAddRepoHostedController(hosted: AddRepoDialogHostedController
|
||||
* composer selection. Hosted mode must close the composer too — leaving it
|
||||
* open would hide the navigation behind a stale project selection. */
|
||||
closeForFolderHandoff: () => void
|
||||
finishProjectAdd: ((repoId: string) => Promise<void>) | undefined
|
||||
finishProjectAdd: CompleteGitRepoAdd | undefined
|
||||
} {
|
||||
const storeCloseModal = useAppStore((s) => s.closeModal)
|
||||
const hostedOnOpenChange = hosted?.onOpenChange
|
||||
const hostedOnProjectAdded = hosted?.onProjectAdded
|
||||
const hostedOnProjectGroupImported = hosted?.onProjectGroupImported
|
||||
// Why: hosted mode (nested inside the workspace composer) must close only
|
||||
// this dialog, never the composer modal that lives in the activeModal slot.
|
||||
const closeModal = useMemo(
|
||||
() => (hostedOnOpenChange ? () => hostedOnOpenChange(false) : storeCloseModal),
|
||||
[hostedOnOpenChange, storeCloseModal]
|
||||
)
|
||||
const finishProjectAdd = useMemo(
|
||||
const finishProjectAdd = useMemo<CompleteGitRepoAdd | undefined>(
|
||||
() =>
|
||||
hostedOnOpenChange && hostedOnProjectAdded
|
||||
? async (repoId: string): Promise<void> => {
|
||||
? async (repoId, _source, _executionHostId, outcome): Promise<void> => {
|
||||
await markOnboardingProjectAdded('addedRepo')
|
||||
hostedOnOpenChange(false)
|
||||
if (
|
||||
outcome?.importedProjectGroupId &&
|
||||
(await hostedOnProjectGroupImported?.(outcome.importedProjectGroupId))
|
||||
) {
|
||||
return
|
||||
}
|
||||
await hostedOnProjectAdded(repoId)
|
||||
}
|
||||
: undefined,
|
||||
[hostedOnOpenChange, hostedOnProjectAdded]
|
||||
[hostedOnOpenChange, hostedOnProjectAdded, hostedOnProjectGroupImported]
|
||||
)
|
||||
const closeForFolderHandoff = useMemo(
|
||||
() =>
|
||||
|
||||
@@ -11,35 +11,37 @@ import { finishProjectAddWithDefaultCheckout } from './project-added-default-che
|
||||
import type { ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { selectWorktreesByRepo } from '@/store/workspace-record-selectors'
|
||||
|
||||
export type GitRepoAddOutcome = { importedProjectGroupId?: string }
|
||||
|
||||
export type CompleteGitRepoAdd = (
|
||||
repoId: string,
|
||||
source: AddRepoExistingWorkspaceSource,
|
||||
executionHostId?: ExecutionHostId,
|
||||
outcome?: GitRepoAddOutcome
|
||||
) => Promise<void>
|
||||
|
||||
type CompleteGitRepoAddOptions = {
|
||||
closeModal: () => void
|
||||
setHideDefaultBranchWorkspace: (hide: boolean) => void
|
||||
/** Why: the nested Add Project flow (hosted inside the workspace composer)
|
||||
* keeps the composer open and selects the new project instead of running
|
||||
* the default-checkout navigation handoff. Telemetry above still applies. */
|
||||
finishProjectAdd?: (
|
||||
repoId: string,
|
||||
source: AddRepoExistingWorkspaceSource,
|
||||
executionHostId?: ExecutionHostId
|
||||
) => Promise<void>
|
||||
finishProjectAdd?: CompleteGitRepoAdd
|
||||
}
|
||||
|
||||
export function useCompleteGitRepoAdd({
|
||||
closeModal,
|
||||
setHideDefaultBranchWorkspace,
|
||||
finishProjectAdd
|
||||
}: CompleteGitRepoAddOptions): (
|
||||
repoId: string,
|
||||
source: AddRepoExistingWorkspaceSource,
|
||||
executionHostId?: ExecutionHostId
|
||||
) => Promise<void> {
|
||||
}: CompleteGitRepoAddOptions): CompleteGitRepoAdd {
|
||||
const detectedTelemetryTrackedRef = useRef<Set<string>>(new Set())
|
||||
|
||||
return useCallback(
|
||||
async (
|
||||
repoId: string,
|
||||
source: AddRepoExistingWorkspaceSource,
|
||||
executionHostId?: ExecutionHostId
|
||||
executionHostId?: ExecutionHostId,
|
||||
outcome?: GitRepoAddOutcome
|
||||
): Promise<void> => {
|
||||
const worktrees = (selectWorktreesByRepo(useAppStore.getState())[repoId] ?? []).filter(
|
||||
(worktree) =>
|
||||
@@ -66,7 +68,7 @@ export function useCompleteGitRepoAdd({
|
||||
track('add_repo_existing_workspaces_detected', existingWorkspaceTelemetry)
|
||||
}
|
||||
if (finishProjectAdd) {
|
||||
await finishProjectAdd(repoId, source, executionHostId)
|
||||
await finishProjectAdd(repoId, source, executionHostId, outcome)
|
||||
return
|
||||
}
|
||||
await finishProjectAddWithDefaultCheckout({
|
||||
|
||||
@@ -248,4 +248,51 @@ describe('useAddRepoNestedImportFlow open folder fallback', () => {
|
||||
})
|
||||
expect(onGitRepoReady).not.toHaveBeenCalled()
|
||||
})
|
||||
it.each(['group', 'separate'] as const)(
|
||||
'carries grouped import outcome without losing SSH owner (%s)',
|
||||
async (mode) => {
|
||||
const importedRepo: Repo = {
|
||||
...folderRepo,
|
||||
id: 'app',
|
||||
kind: 'git',
|
||||
path: scan.repos[0].path,
|
||||
connectionId: 'ssh-builder'
|
||||
}
|
||||
mocks.state.repos = [importedRepo]
|
||||
const onGitRepoReady = vi.fn()
|
||||
const result: ProjectGroupImportResult = {
|
||||
group: {
|
||||
id: 'group',
|
||||
name: 'platform',
|
||||
parentPath: scan.selectedPath,
|
||||
connectionId: 'ssh-builder',
|
||||
parentGroupId: null,
|
||||
createdFrom: 'folder-scan',
|
||||
tabOrder: 0,
|
||||
isCollapsed: false,
|
||||
color: null,
|
||||
createdAt: 1,
|
||||
updatedAt: 1
|
||||
},
|
||||
projects: [{ path: importedRepo.path, projectId: importedRepo.id, status: 'imported' }],
|
||||
importedCount: 1,
|
||||
alreadyKnownCount: 0,
|
||||
failedCount: 0
|
||||
}
|
||||
const flow = useTestAddRepoNestedImportFlow({
|
||||
nestedConnectionId: 'ssh-builder',
|
||||
nestedRuntimeKind: 'ssh',
|
||||
nestedSelectedPaths: new Set([importedRepo.path]),
|
||||
importNestedRepos: vi.fn().mockResolvedValue(result),
|
||||
onGitRepoReady
|
||||
})
|
||||
await flow.handleImportNestedRepos(mode)
|
||||
expect(onGitRepoReady).toHaveBeenCalledWith(
|
||||
importedRepo.id,
|
||||
'ssh_remote_path',
|
||||
'ssh:ssh-builder',
|
||||
mode === 'group' ? { importedProjectGroupId: 'group' } : undefined
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -19,7 +19,7 @@ import { translate } from '@/i18n/i18n'
|
||||
import { worktreeRefreshOptions } from './add-repo-runtime-owner'
|
||||
import { completeNestedFolderOpen } from './complete-nested-folder-open'
|
||||
import { defaultProjectGroupNameForPath } from './add-repo-dialog-types'
|
||||
import type { ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import type { CompleteGitRepoAdd } from './use-complete-git-repo-add'
|
||||
|
||||
export function useAddRepoNestedImportFlow({
|
||||
nestedAttemptId,
|
||||
@@ -59,11 +59,7 @@ export function useAddRepoNestedImportFlow({
|
||||
mode: 'group' | 'separate'
|
||||
}) => Promise<ProjectGroupImportResult | null>
|
||||
getNestedRepoRuntimeKind: (connectionId: string | null) => NestedRepoTelemetryRuntimeKind
|
||||
onGitRepoReady: (
|
||||
repoId: string,
|
||||
source: AddRepoExistingWorkspaceSource,
|
||||
executionHostId?: ExecutionHostId
|
||||
) => Promise<void>
|
||||
onGitRepoReady: CompleteGitRepoAdd
|
||||
setIsAdding: (isAdding: boolean) => void
|
||||
}): {
|
||||
handleImportNestedRepos: (mode: 'group' | 'separate') => Promise<void>
|
||||
@@ -208,7 +204,14 @@ export function useAddRepoNestedImportFlow({
|
||||
: activeRuntimeEnvironmentId?.trim()
|
||||
? 'runtime_server_path'
|
||||
: 'local_folder_picker'
|
||||
await onGitRepoReady(repo.id, source, completionOwnerOptions.executionHostId)
|
||||
await onGitRepoReady(
|
||||
repo.id,
|
||||
source,
|
||||
completionOwnerOptions.executionHostId,
|
||||
mode === 'group' && result.group
|
||||
? { importedProjectGroupId: result.group.id }
|
||||
: undefined
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
if (gen === nestedImportGenRef.current) {
|
||||
|
||||
@@ -62,6 +62,7 @@ export type ComposerSourceModel = {
|
||||
onConnectSelectedRepo: () => Promise<void>
|
||||
prefetchSshConnectedGeneration: number
|
||||
resolvePendingSmartGitHubSubmit: () => Promise<PendingSmartGitHubSubmitResolution>
|
||||
selectAddedProjectGroup: (groupId: string) => boolean
|
||||
selectAddedProjectRepo: (nextRepoId: string) => void
|
||||
showProjectRequiredError: () => void
|
||||
smartNameSelection: SmartWorkspaceNameSelection | null
|
||||
|
||||
@@ -22,7 +22,7 @@ export type ComposerSourceState = {
|
||||
ComposerModel,
|
||||
'handleRepoChange' | 'handleFolderSourceRepoChange' | 'handleProjectHostSetupChange'
|
||||
>
|
||||
projectTargetActions: Pick<ComposerModel, 'handleProjectChange'>
|
||||
projectTargetActions: Pick<ComposerModel, 'handleProjectChange' | 'selectAddedProjectGroup'>
|
||||
branchStartPointActions: Pick<
|
||||
ComposerModel,
|
||||
| 'showProjectRequiredError'
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { act, cleanup, renderHook } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { useAppStore } from '@/store'
|
||||
import type { ProjectGroup } from '../../../../shared/project-group-types'
|
||||
import { pr, repo, useReviewBaseSelectionFixture } from './composer-review-base-test-fixture'
|
||||
|
||||
const originalState = useAppStore.getState()
|
||||
const group: ProjectGroup = {
|
||||
id: 'just-imported',
|
||||
name: 'Imported group',
|
||||
parentPath: '/imported',
|
||||
parentGroupId: null,
|
||||
createdFrom: 'folder-scan',
|
||||
tabOrder: 0,
|
||||
isCollapsed: false,
|
||||
color: null,
|
||||
createdAt: 1,
|
||||
updatedAt: 1
|
||||
}
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
useAppStore.setState(originalState, true)
|
||||
})
|
||||
|
||||
describe('imported group composer selection', () => {
|
||||
it('selects a group created after render through the same reset path as the picker', () => {
|
||||
const { result } = renderHook(() => useReviewBaseSelectionFixture(pr))
|
||||
const importedRepo = {
|
||||
...repo,
|
||||
id: 'imported-repo',
|
||||
path: '/imported/app',
|
||||
projectGroupId: group.id
|
||||
}
|
||||
act(() => useAppStore.setState({ projectGroups: [group], repos: [importedRepo] }))
|
||||
let selected = false
|
||||
act(() => {
|
||||
selected = result.current.selectAddedProjectGroup(group.id)
|
||||
})
|
||||
expect(selected).toBe(true)
|
||||
expect(result.current.selectedProjectGroupId).toBe(group.id)
|
||||
expect(result.current.repoId).toBe(importedRepo.id)
|
||||
expect(result.current.linkedWorkItem).toBeNull()
|
||||
expect(result.current.baseBranch).toBeUndefined()
|
||||
expect(result.current.branchNameOverride).toBeUndefined()
|
||||
expect(result.current.pushTarget).toBeUndefined()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ ...group, id: 'different' },
|
||||
{ ...group, parentPath: null },
|
||||
{ ...group, executionHostId: 'ssh:missing' }
|
||||
])(
|
||||
'declines missing, non-folder or unreachable groups without changing selection (%j)',
|
||||
(unavailable) => {
|
||||
const { result } = renderHook(() => useReviewBaseSelectionFixture(pr))
|
||||
act(() => useAppStore.setState({ projectGroups: [unavailable] }))
|
||||
let selected = true
|
||||
act(() => {
|
||||
selected = result.current.selectAddedProjectGroup(group.id)
|
||||
})
|
||||
expect(selected).toBe(false)
|
||||
expect(result.current.selectedProjectGroupId).toBeNull()
|
||||
expect(result.current.repoId).toBe(repo.id)
|
||||
expect(result.current.linkedWorkItem).toMatchObject({ type: 'pr', number: pr.number })
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -1,3 +1,6 @@
|
||||
import { useAppStore } from '@/store'
|
||||
import type { ProjectGroup } from '../../../../shared/project-group-types'
|
||||
import type { Repo } from '../../../../shared/repo-types'
|
||||
import type { ComposerModel } from './composer-model'
|
||||
|
||||
type ProjectTargetActionsInput = Pick<
|
||||
@@ -84,6 +87,82 @@ export function useProjectTargetActions(input: ProjectTargetActionsInput) {
|
||||
workspaceHostScope
|
||||
} = input
|
||||
|
||||
const applyProjectGroupSelection = useCallback(
|
||||
(
|
||||
group: ProjectGroup,
|
||||
sourceRepos: readonly Repo[],
|
||||
sourceGroups: readonly ProjectGroup[]
|
||||
): void => {
|
||||
const nextSourceRepo = getFolderSourceRepos(sourceRepos, sourceGroups, group)[0]
|
||||
smartSourceSelectionRef.current = null
|
||||
setSelectedProjectGroupId(group.id)
|
||||
setProjectError(null)
|
||||
setRepoId(nextSourceRepo?.id ?? '')
|
||||
setLinkedIssue('')
|
||||
setLinkedPR(null)
|
||||
setLinkedGitLabIssue(null)
|
||||
setLinkedGitLabMR(null)
|
||||
if (linkedWorkItem && !shouldPreserveWorkspaceSourceOnRepoChange(linkedWorkItem)) {
|
||||
setLinkedWorkItem(null)
|
||||
setLinkedTaskSourceContext(null)
|
||||
}
|
||||
setSparseEnabled(false)
|
||||
setSparseDirectories('')
|
||||
setSparseSelectedPresetId(null)
|
||||
setBaseBranch(undefined)
|
||||
setPushTarget(undefined)
|
||||
setBranchNameOverride(undefined)
|
||||
// Why (#5181): clear branch-scoped reuse state on a project switch too.
|
||||
setBranchNameOverridePreservesNameEdits(false)
|
||||
setReuseEligibleBranch(null)
|
||||
setReuseSelectedBranch(false)
|
||||
setForkPushWarning(null)
|
||||
setStartFromResetHint(null)
|
||||
},
|
||||
[
|
||||
linkedWorkItem,
|
||||
setRepoId,
|
||||
setSelectedProjectGroupId,
|
||||
setProjectError,
|
||||
setLinkedIssue,
|
||||
setLinkedPR,
|
||||
setLinkedGitLabIssue,
|
||||
setLinkedGitLabMR,
|
||||
setLinkedWorkItem,
|
||||
setLinkedTaskSourceContext,
|
||||
setSparseEnabled,
|
||||
setSparseDirectories,
|
||||
setSparseSelectedPresetId,
|
||||
setBaseBranch,
|
||||
setPushTarget,
|
||||
setBranchNameOverride,
|
||||
setBranchNameOverridePreservesNameEdits,
|
||||
setReuseEligibleBranch,
|
||||
setReuseSelectedBranch,
|
||||
setForkPushWarning,
|
||||
setStartFromResetHint,
|
||||
smartSourceSelectionRef
|
||||
]
|
||||
)
|
||||
|
||||
const selectAddedProjectGroup = useCallback(
|
||||
(groupId: string): boolean => {
|
||||
const state = useAppStore.getState()
|
||||
const group = findActionableFolderProjectGroup({
|
||||
projectGroups: state.projectGroups,
|
||||
groupId,
|
||||
actionableHostIds
|
||||
})
|
||||
if (!group) {
|
||||
return false
|
||||
}
|
||||
initialProjectGroupAppliedRef.current = true
|
||||
applyProjectGroupSelection(group, state.repos, state.projectGroups)
|
||||
return true
|
||||
},
|
||||
[actionableHostIds, initialProjectGroupAppliedRef, applyProjectGroupSelection]
|
||||
)
|
||||
|
||||
const handleProjectChange = useCallback(
|
||||
(projectId: string): void => {
|
||||
initialProjectGroupAppliedRef.current = true
|
||||
@@ -104,31 +183,7 @@ export function useProjectTargetActions(input: ProjectTargetActionsInput) {
|
||||
)
|
||||
return
|
||||
}
|
||||
const nextSourceRepo = getFolderSourceRepos(repos, projectGroups, nextProjectGroup)[0]
|
||||
smartSourceSelectionRef.current = null
|
||||
setSelectedProjectGroupId(nextProjectGroup.id)
|
||||
setProjectError(null)
|
||||
setRepoId(nextSourceRepo?.id ?? '')
|
||||
setLinkedIssue('')
|
||||
setLinkedPR(null)
|
||||
setLinkedGitLabIssue(null)
|
||||
setLinkedGitLabMR(null)
|
||||
if (linkedWorkItem && !shouldPreserveWorkspaceSourceOnRepoChange(linkedWorkItem)) {
|
||||
setLinkedWorkItem(null)
|
||||
setLinkedTaskSourceContext(null)
|
||||
}
|
||||
setSparseEnabled(false)
|
||||
setSparseDirectories('')
|
||||
setSparseSelectedPresetId(null)
|
||||
setBaseBranch(undefined)
|
||||
setPushTarget(undefined)
|
||||
setBranchNameOverride(undefined)
|
||||
// Why (#5181): clear branch-scoped reuse state on a project switch too.
|
||||
setBranchNameOverridePreservesNameEdits(false)
|
||||
setReuseEligibleBranch(null)
|
||||
setReuseSelectedBranch(false)
|
||||
setForkPushWarning(null)
|
||||
setStartFromResetHint(null)
|
||||
applyProjectGroupSelection(nextProjectGroup, repos, projectGroups)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -150,43 +205,25 @@ export function useProjectTargetActions(input: ProjectTargetActionsInput) {
|
||||
handleRepoChange(nextRepoId, { forceResetStartFrom: isProjectGroupTarget })
|
||||
},
|
||||
[
|
||||
applyProjectGroupSelection,
|
||||
eligibleRepos,
|
||||
actionableHostIds,
|
||||
handleRepoChange,
|
||||
isProjectGroupTarget,
|
||||
linkedWorkItem,
|
||||
projectGroups,
|
||||
projectHostSetups,
|
||||
projects,
|
||||
repos,
|
||||
setRepoId,
|
||||
selectedWorkspaceTarget,
|
||||
workspaceHostScope,
|
||||
initialProjectGroupAppliedRef,
|
||||
setBaseBranch,
|
||||
setBranchNameOverride,
|
||||
setBranchNameOverridePreservesNameEdits,
|
||||
setForkPushWarning,
|
||||
setLinkedGitLabIssue,
|
||||
setLinkedGitLabMR,
|
||||
setLinkedIssue,
|
||||
setLinkedPR,
|
||||
setLinkedTaskSourceContext,
|
||||
setLinkedWorkItem,
|
||||
setProjectError,
|
||||
setPushTarget,
|
||||
setReuseEligibleBranch,
|
||||
setReuseSelectedBranch,
|
||||
setSelectedProjectGroupId,
|
||||
setSparseDirectories,
|
||||
setSparseEnabled,
|
||||
setSparseSelectedPresetId,
|
||||
setStartFromResetHint,
|
||||
smartSourceSelectionRef
|
||||
setProjectError
|
||||
]
|
||||
)
|
||||
|
||||
return {
|
||||
handleProjectChange
|
||||
handleProjectChange,
|
||||
selectAddedProjectGroup
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ export type UseComposerStateResult = {
|
||||
nameInputRef: RefObject<HTMLInputElement | null>
|
||||
submitQuick: (agent: TuiAgent | null, options?: QuickSubmitOptions) => Promise<void>
|
||||
createDisabled: boolean
|
||||
selectAddedProjectGroup: (groupId: string) => boolean
|
||||
selectAddedProjectRepo: (repoId: string) => void
|
||||
}
|
||||
|
||||
@@ -104,6 +105,7 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
nameInputRef: model.nameInputRef,
|
||||
submitQuick: model.submitQuick,
|
||||
createDisabled,
|
||||
selectAddedProjectRepo: model.selectAddedProjectRepo
|
||||
selectAddedProjectRepo: model.selectAddedProjectRepo,
|
||||
selectAddedProjectGroup: model.selectAddedProjectGroup
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user