Add pending host setup action in project settings

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-11 12:33:59 -07:00
co-authored by Orca
parent c430369b12
commit a0682f082f
10 changed files with 109 additions and 3 deletions
@@ -504,6 +504,8 @@ Already partially landed:
runtime RPC, and CLI for independent setup/provisioning metadata
- renderer-store lifecycle actions for project-host setup create, update, and
delete, routed by setup-owning host
- project settings action for creating pending setup/provisioning metadata on a
known host before a checkout path exists
Not complete yet:
@@ -508,6 +508,8 @@ Already partially landed:
runtime RPC, and CLI for independent setup/provisioning metadata
- renderer-store lifecycle actions for project-host setup create, update, and
delete, routed by setup-owning host
- project settings action for creating pending setup/provisioning metadata on a
known host before a checkout path exists
Not complete yet:
@@ -426,6 +426,8 @@ Already partially landed:
runtime RPC, and CLI for independent setup/provisioning metadata
- renderer-store lifecycle actions for project-host setup create, update, and
delete, routed by setup-owning host
- project settings action for creating pending setup/provisioning metadata on a
known host before a checkout path exists
Still needed:
+6 -3
View File
@@ -785,6 +785,9 @@ Landed so far:
- Added renderer-store lifecycle actions for first-class setup create, update,
and delete. Runtime-owned setups route through their owning runtime instead
of whichever host is currently focused.
- Added a project settings action to create pending project-host setup metadata
for a known host without requiring an existing checkout path. This lets the UI
represent setup/provisioning intent before import/clone/provisioning finishes.
- Added tests for local repos, SSH repos, same-provider multi-host grouping,
no-identity same-name non-grouping, selector cache behavior, persistence
backfill, repo mutation synchronization, renderer hydration, runtime RPC
@@ -815,8 +818,8 @@ Important limitation:
across load and repo mutations, but the UI/API still mostly creates setups
through repo-backed import/clone paths. First-class setup update now exists,
first-class setup create/update/delete now exist for independent metadata and
are exposed through the renderer store, but actual provisioning execution and
full UI flows are still future work.
are exposed through the renderer store/settings, but actual provisioning
execution and full UI flows are still future work.
Remaining end-to-end work:
@@ -824,7 +827,7 @@ Remaining end-to-end work:
- finish SSH clone streamed-progress parity, provisioning, and bulk setup-on-host
flows
- add actual provisioning execution APIs instead of only recording independent
setup/provisioning metadata
setup/provisioning metadata and intent
- split settings into explicit client, host, project, and project-host setup
scopes
- validate the default project-first sidebar view in Electron and continue
@@ -266,4 +266,58 @@ describe('RepositoryHostSetupsSection', () => {
expect(openSettingsPage).toHaveBeenCalledTimes(1)
expect(openSettingsTarget).toHaveBeenCalledWith({ pane: 'repo', repoId: 'remote-repo' })
})
it('creates pending setup metadata for a known host without requiring a path', async () => {
const createProjectHostSetup = vi.fn().mockResolvedValue({
project: makeProject({ id: 'github:stablyai/orca' }),
setup: makeSetup({
id: 'gpu-setup',
projectId: 'github:stablyai/orca',
repoId: '',
hostId: 'runtime:gpu',
path: '',
setupState: 'not-set-up',
setupMethod: 'provisioned'
})
})
const localRepo = makeRepo({
id: 'local-repo',
displayName: 'Orca',
path: '/Users/alice/orca'
})
useAppStore.setState({
repos: [localRepo],
projects: [makeProject({ id: 'github:stablyai/orca' })],
projectHostSetups: [
makeSetup({
id: 'local-repo',
projectId: 'github:stablyai/orca',
repoId: 'local-repo',
hostId: 'local',
path: '/Users/alice/orca'
})
],
settings: { activeRuntimeEnvironmentId: 'gpu' } as never,
createProjectHostSetup
})
renderSection(localRepo)
const trackButton = Array.from(container.querySelectorAll('button')).find(
(button) => button.textContent === 'Track setup'
)
expect(trackButton).toBeTruthy()
await act(async () => {
trackButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
})
expect(createProjectHostSetup).toHaveBeenCalledWith({
projectId: 'github:stablyai/orca',
hostId: 'runtime:gpu',
displayName: 'Orca',
setupState: 'not-set-up',
setupMethod: 'provisioned'
})
})
})
@@ -98,6 +98,7 @@ export function RepositoryHostSetupsSection({
const openSettingsPage = useAppStore((state) => state.openSettingsPage)
const openSettingsTarget = useAppStore((state) => state.openSettingsTarget)
const setupProjectExistingFolder = useAppStore((state) => state.setupProjectExistingFolder)
const createProjectHostSetup = useAppStore((state) => state.createProjectHostSetup)
const deleteProjectHostSetup = useAppStore((state) => state.deleteProjectHostSetup)
const sshTargetLabels = useAppStore((state) => state.sshTargetLabels)
const activeRuntimeEnvironmentId = useAppStore(
@@ -123,6 +124,7 @@ export function RepositoryHostSetupsSection({
const [setupPath, setSetupPath] = useState('')
const [setupKind, setSetupKind] = useState<'git' | 'folder'>('git')
const [isSettingUp, setIsSettingUp] = useState(false)
const [isCreatingPendingSetup, setIsCreatingPendingSetup] = useState(false)
const [deletingSetupId, setDeletingSetupId] = useState<string | null>(null)
const setupTargetHostId = selectedSetupHostId ?? setupHostOptions[0]?.id ?? null
@@ -309,6 +311,39 @@ export function RepositoryHostSetupsSection({
? translate('auto.components.settings.RepositoryPane.settingUpHost', 'Importing...')
: translate('auto.components.settings.RepositoryPane.setupHost', 'Import')}
</Button>
<Button
type="button"
variant="outline"
size="sm"
disabled={!setupTargetHostId || isCreatingPendingSetup}
onClick={async () => {
if (!setupTargetHostId || !selectedProjectHostSetup) {
return
}
setIsCreatingPendingSetup(true)
const result = await createProjectHostSetup({
projectId: selectedProjectHostSetup.projectId,
hostId: setupTargetHostId,
displayName: repo.displayName,
setupState: 'not-set-up',
setupMethod: 'provisioned'
})
setIsCreatingPendingSetup(false)
if (result) {
setSelectedSetupHostId(null)
}
}}
>
{isCreatingPendingSetup
? translate(
'auto.components.settings.RepositoryPane.creatingPendingSetup',
'Creating...'
)
: translate(
'auto.components.settings.RepositoryPane.createPendingSetup',
'Track setup'
)}
</Button>
</div>
</div>
) : null}
+2
View File
@@ -5065,6 +5065,8 @@
"setupKindFolder": "Folder",
"settingUpHost": "Importing...",
"setupHost": "Import",
"creatingPendingSetup": "Creating...",
"createPendingSetup": "Track setup",
"499a437335": "Identity"
},
"RepositorySourceControlAiActionRows": {
+2
View File
@@ -5045,6 +5045,8 @@
"setupKindFolder": "フォルダー",
"settingUpHost": "インポート中...",
"setupHost": "インポート",
"creatingPendingSetup": "作成中...",
"createPendingSetup": "セットアップを追跡",
"499a437335": "身元"
},
"RepositorySourceControlAiActionRows": {
+2
View File
@@ -5045,6 +5045,8 @@
"setupKindFolder": "폴더",
"settingUpHost": "가져오는 중...",
"setupHost": "가져오기",
"creatingPendingSetup": "생성 중...",
"createPendingSetup": "설정 추적",
"499a437335": "신원"
},
"RepositorySourceControlAiActionRows": {
+2
View File
@@ -5045,6 +5045,8 @@
"setupKindFolder": "文件夹",
"settingUpHost": "正在导入...",
"setupHost": "导入",
"creatingPendingSetup": "正在创建...",
"createPendingSetup": "跟踪设置",
"499a437335": "身份"
},
"RepositorySourceControlAiActionRows": {