diff --git a/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx b/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx index c639082d581..874708be52f 100644 --- a/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx +++ b/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx @@ -56,6 +56,13 @@ vi.mock('@/components/agent/AgentCombobox', () => ({ default: () => })) +// Stub the host-add dialog to its `mode` — the composer's job is to open it with the right +// mode; the dialog's own SSH/runtime IPC has separate coverage. +vi.mock('@/components/sidebar/AddRemoteHostDialog', () => ({ + AddRemoteHostDialog: ({ mode }: { mode: 'ssh' | 'server' | null }) => + mode ?
: null +})) + vi.mock('@/components/sparse/SparseCheckoutPresetSelect', () => ({ default: () => })) @@ -148,7 +155,8 @@ const devboxNeedsSetupHostOption: ProjectHostSetupOption = { hostId: 'ssh:devbox', label: 'Devbox', detail: 'Project not set up on this host', - isAvailable: true + isAvailable: true, + attention: false } const disconnectedDevboxNeedsSetupHostOption: ProjectHostSetupOption = { @@ -159,9 +167,29 @@ const disconnectedDevboxNeedsSetupHostOption: ProjectHostSetupOption = { label: 'Devbox', detail: 'Connect this host to set up projects', isAvailable: false, + attention: false, connectAction: { kind: 'ssh', targetId: 'devbox' } } +const disconnectedBastionNeedsSetupHostOption: ProjectHostSetupOption = { + kind: 'needs-setup', + id: 'needs-setup:ssh:bastion', + projectId: 'project-group:platform', + hostId: 'ssh:bastion', + label: 'Bastion', + detail: 'Connect this host to set up projects', + isAvailable: false, + attention: false, + connectAction: { kind: 'ssh', targetId: 'bastion' } +} + +function findConnectButton(label: string): HTMLButtonElement | undefined { + const item = findRunTargetItem(label) + return [...(item?.querySelectorAll('button') ?? [])].find((button) => + button.textContent?.includes('Connect') + ) +} + function renderCard( overrides: Partial
- {setupConfig.command}
-
- ++@@ -798,10 +867,7 @@ export default function NewWorkspaceComposerCard({ useTranslation() const { isFileDragOver, dragHandlers } = useComposerFileDragOver() const openModal = useAppStore((s) => s.openModal) - const closeModal = useAppStore((s) => s.closeModal) const activeModal = useAppStore((s) => s.activeModal) - const openSettingsPage = useAppStore((s) => s.openSettingsPage) - const openSettingsTarget = useAppStore((s) => s.openSettingsTarget) const defaultTuiAgent = useAppStore((s) => s.settings?.defaultTuiAgent ?? null) const disabledTuiAgents = useAppStore((s) => s.settings?.disabledTuiAgents ?? []) const updateSettings = useAppStore((s) => s.updateSettings) @@ -909,26 +975,16 @@ export default function NewWorkspaceComposerCard({ const handleAddRepo = React.useCallback((): void => { openModal('add-repo') }, [openModal]) - const openHostSettingsTarget = React.useCallback( - (target: 'add-remote-orca-server' | 'add-ssh-host'): void => { - // Why: the composer can be modal; close it before deep-linking to the - // existing host-management forms in Settings. - closeModal() - openSettingsPage() - openSettingsTarget({ - pane: target === 'add-ssh-host' ? 'ssh' : 'servers', - repoId: null, - intent: target - }) - }, - [closeModal, openSettingsPage, openSettingsTarget] - ) + // Why: open the host-add form inline over the composer (not via Settings) so the user's + // in-progress workspace form survives; the new host lands in the store and flows straight + // back into the run-target picker without a navigation round-trip. + const [addRemoteHostMode, setAddRemoteHostMode] = React.useState{setupConfig.command}(null) const handleAddSshHost = React.useCallback((): void => { - openHostSettingsTarget('add-ssh-host') - }, [openHostSettingsTarget]) + setAddRemoteHostMode('ssh') + }, []) const handleAddRemoteServer = React.useCallback((): void => { - openHostSettingsTarget('add-remote-orca-server') - }, [openHostSettingsTarget]) + setAddRemoteHostMode('server') + }, []) const handleConnectRunTargetHost = React.useCallback( async (option: NeedsSetupProjectHostOption): Promise => { const action = option.connectAction @@ -937,7 +993,10 @@ export default function NewWorkspaceComposerCard({ } try { if (action.kind === 'ssh') { - await window.api.ssh.connect({ targetId: action.targetId }) + // Why: ssh.connect has no built-in timeout; a stalled connect would otherwise leave the + // row's spinner/disabled state stuck forever. Bound the UI wait — the backend keeps + // connecting and the picker updates from store SSH state if it later succeeds. + await withUiConnectTimeout(window.api.ssh.connect({ targetId: action.targetId })) return } @@ -1340,6 +1399,9 @@ export default function NewWorkspaceComposerCard({ with the content or footer below it while preserving normal spacing from the Agent field above. */} + {/* Why: -ml-2 pulls the button so its label aligns flush-left with the field labels above + while the padded hover highlight extends past the label on the left. The scroll + container's px-2 inset gives that overhang room so it isn't clipped. */}) : null} @@ -1685,6 +1747,10 @@ export default function NewWorkspaceComposerCard({
+ {translate( + 'auto.components.settings.SshTargetForm.3b01ca44a0', + 'Optional. Used for tunneling (e.g. Cloudflare Access, ProxyCommand).' + )} +
++ {translate( + 'auto.components.settings.SshTargetForm.feae1d1e69', + 'Optional. Equivalent to ProxyJump / ssh -J.' + )} +
++ {translate( + 'auto.components.settings.SshTargetForm.53e9aabfc0', + 'Uses OpenSSH multiplexing when available. Turn off for hosts with custom SSH restrictions.' + )} +
++ {translate( + 'auto.components.settings.SshTargetForm.b574994adc', + 'Use End Remote Terminals or Reset Relay when you want to stop them.' + )} +
++ {translate( + 'auto.components.settings.SshTargetForm.1b19b00e93', + 'Bounded timeouts must be between 60 seconds and 7 days.' + )} +
+- {translate( - 'auto.components.settings.SshTargetForm.3b01ca44a0', - 'Optional. Used for tunneling (e.g. Cloudflare Access, ProxyCommand).' - )} -
-- {translate( - 'auto.components.settings.SshTargetForm.feae1d1e69', - 'Optional. Equivalent to ProxyJump / ssh -J.' - )} -
-- {translate( - 'auto.components.settings.SshTargetForm.53e9aabfc0', - 'Uses OpenSSH multiplexing when available. Turn off for hosts with custom SSH restrictions.' - )} -
-- {translate( - 'auto.components.settings.SshTargetForm.b574994adc', - 'Use End Remote Terminals or Reset Relay when you want to stop them.' - )} -
-- {translate( - 'auto.components.settings.SshTargetForm.1b19b00e93', - 'Bounded timeouts must be between 60 seconds and 7 days.' - )} -
-