diff --git a/notes/project-first-host-model-change-inventory.md b/notes/project-first-host-model-change-inventory.md index 9447f1e7461..92b09298ca5 100644 --- a/notes/project-first-host-model-change-inventory.md +++ b/notes/project-first-host-model-change-inventory.md @@ -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: diff --git a/notes/project-first-host-model-conversation.md b/notes/project-first-host-model-conversation.md index bfffe00f8a8..067bb75658f 100644 --- a/notes/project-first-host-model-conversation.md +++ b/notes/project-first-host-model-conversation.md @@ -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: diff --git a/notes/project-first-host-model-handoff.md b/notes/project-first-host-model-handoff.md index 70dba30246b..39088c38ad8 100644 --- a/notes/project-first-host-model-handoff.md +++ b/notes/project-first-host-model-handoff.md @@ -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: diff --git a/notes/project-first-host-model.md b/notes/project-first-host-model.md index 283bcfd8ed9..3c4015d8b30 100644 --- a/notes/project-first-host-model.md +++ b/notes/project-first-host-model.md @@ -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 diff --git a/src/renderer/src/components/settings/RepositoryHostSetupsSection.test.tsx b/src/renderer/src/components/settings/RepositoryHostSetupsSection.test.tsx index 5e4371aad7a..405cd675ca8 100644 --- a/src/renderer/src/components/settings/RepositoryHostSetupsSection.test.tsx +++ b/src/renderer/src/components/settings/RepositoryHostSetupsSection.test.tsx @@ -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' + }) + }) }) diff --git a/src/renderer/src/components/settings/RepositoryHostSetupsSection.tsx b/src/renderer/src/components/settings/RepositoryHostSetupsSection.tsx index 3d739130af9..58405fc1ce9 100644 --- a/src/renderer/src/components/settings/RepositoryHostSetupsSection.tsx +++ b/src/renderer/src/components/settings/RepositoryHostSetupsSection.tsx @@ -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(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')} + ) : null} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 80eb963b416..de679b5bbde 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -5065,6 +5065,8 @@ "setupKindFolder": "Folder", "settingUpHost": "Importing...", "setupHost": "Import", + "creatingPendingSetup": "Creating...", + "createPendingSetup": "Track setup", "499a437335": "Identity" }, "RepositorySourceControlAiActionRows": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 4794b31d1ca..fe4686746af 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -5045,6 +5045,8 @@ "setupKindFolder": "フォルダー", "settingUpHost": "インポート中...", "setupHost": "インポート", + "creatingPendingSetup": "作成中...", + "createPendingSetup": "セットアップを追跡", "499a437335": "身元" }, "RepositorySourceControlAiActionRows": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index a49f9d3f4bd..f1bce81e930 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -5045,6 +5045,8 @@ "setupKindFolder": "폴더", "settingUpHost": "가져오는 중...", "setupHost": "가져오기", + "creatingPendingSetup": "생성 중...", + "createPendingSetup": "설정 추적", "499a437335": "신원" }, "RepositorySourceControlAiActionRows": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index f3142c3260c..8066e5135e6 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -5045,6 +5045,8 @@ "setupKindFolder": "文件夹", "settingUpHost": "正在导入...", "setupHost": "导入", + "creatingPendingSetup": "正在创建...", + "createPendingSetup": "跟踪设置", "499a437335": "身份" }, "RepositorySourceControlAiActionRows": {