fix(ssh): say what plain SSH is for beside its button, and polish the plain SSH window (#31732)

* fix(ssh): point unclearable move conflicts at plain SSH, and polish the plain SSH window

Conflicts retrying can't clear now say how to keep working on the host. The
plain SSH terminal paints its theme background past the grid, and a failed
open shows only its error, not an empty terminal.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): say what plain SSH is for beside its button, not in main's refusal text

Main's refusal text also reaches clients without a plain SSH terminal, so the
next step is shown next to the button wherever it renders. Also: a translucent
theme isn't painted twice, and a failed open shrinks to its error.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): treat only opacity below 1 as a translucent plain SSH background

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Neil
2026-10-21 23:06:39 -07:00
committed by GitHub
co-authored by Claude
parent 051221c3bf
commit 57ecdc2ca2
6 changed files with 79 additions and 11 deletions
@@ -0,0 +1,38 @@
import { describe, expect, it } from 'vitest'
import { orcadMigrationConflictGuidance } from './orcad-migration-conflict-guidance'
const NO_DESKTOP_STEP = [
'project_group_id',
'folder_workspace_id',
'dormant_id',
'dormant_focus',
'client_state',
'close_intent_destination',
'snapshot_destination'
]
describe('orcad migration conflict guidance', () => {
// Why no UI words here: this text also reaches clients that have no plain SSH terminal.
it('names the code for every conflict retrying can’t clear, and no client-only action', () => {
for (const kind of NO_DESKTOP_STEP) {
const text = orcadMigrationConflictGuidance(
new Error(`orcad_migration_${kind}_conflict:ui:workspaceHostScope`)
)
expect(text).toContain('Retrying won’t clear it.')
expect(text).not.toContain('plain SSH')
expect(text).toContain(`orcad_migration_${kind}_conflict`)
}
})
it('keeps the specific way out where the desktop has one', () => {
expect(
orcadMigrationConflictGuidance(
new Error('orcad_migration_repository_path_conflict:/home/me/app')
)
).toContain('remove that project from this desktop and connect again')
})
it('is null for anything that is not a conflict', () => {
expect(orcadMigrationConflictGuidance(new Error('ECONNRESET'))).toBeNull()
})
})
@@ -23,13 +23,22 @@ export function OpenPlainSshTerminalButton({
return null
}
return (
<Button
type="button"
size="xs"
variant="outline"
onClick={() => showPlainSshPanel(targetId, targetLabel)}
>
{translate('auto.components.plainSshTerminal.open', 'Open plain SSH terminal')}
</Button>
<span className="inline-flex flex-wrap items-center gap-2">
<Button
type="button"
size="xs"
variant="outline"
onClick={() => showPlainSshPanel(targetId, targetLabel)}
>
{translate('auto.components.plainSshTerminal.open', 'Open plain SSH terminal')}
</Button>
{/* The next step for any block, including ones retrying can't clear. */}
<span className="text-xs text-muted-foreground">
{translate(
'auto.components.plainSshTerminal.openHint',
'Keeps you working on the host while Orca can’t set up there.'
)}
</span>
</span>
)
}
@@ -241,6 +241,7 @@ describe('plain SSH terminal', () => {
render(<Harness status={blocked} />)
await user.click(screen.getByRole('button', { name: 'Open plain SSH terminal' }))
expect(await screen.findByText(/couldn’t find OpenSSH/)).toBeInTheDocument()
expect(document.querySelector('[data-plain-ssh-screen]')).toHaveClass('hidden')
})
it('hangs up when its window closes', async () => {
@@ -76,6 +76,8 @@ function PlainSshTerminalCard({ panel }: { panel: PlainSshPanel }): React.JSX.El
// Why hidden, not unmounted: minimizing must keep the SSH session and its scrollback.
className={cn(
'pointer-events-auto flex h-[26rem] w-[min(44rem,calc(100vw-2rem))] flex-col overflow-hidden rounded-xl border border-border bg-card text-card-foreground shadow-floating',
// A failed open has only its error to show.
phase.kind === 'failed' && 'h-auto',
panel.minimized && 'hidden'
)}
>
@@ -105,7 +107,11 @@ function PlainSshTerminalCard({ panel }: { panel: PlainSshPanel }): React.JSX.El
{message ? (
<p className="px-3 py-1.5 text-xs text-destructive [overflow-wrap:anywhere]">{message}</p>
) : null}
<div className="min-h-0 flex-1">
{/* An empty terminal under a failed open's error would read as a live session. */}
<div
data-plain-ssh-screen
className={cn('min-h-0 flex-1', phase.kind === 'failed' && 'hidden')}
>
<PlainSshTerminalView
targetId={panel.targetId}
hidden={panel.minimized}
@@ -314,5 +314,18 @@ export function PlainSshTerminalView({
}
}, [hidden])
return <div ref={containerRef} className="size-full min-h-0 overflow-hidden px-2 py-1" />
// Why the theme's background: fit leaves a remainder of pixels beside the grid that must not show the card.
const themeBackground = resolveThemeColors(settings, systemPrefersDark).theme?.background
// A translucent theme is painted once by xterm already; painting it again would darken the grid.
// Same test as xterm's allowTransparency (preview-terminal-options), not the rgba form opacity 1 also takes.
const translucent =
settings?.terminalBackgroundOpacity !== undefined && settings.terminalBackgroundOpacity < 1
const background = translucent ? undefined : themeBackground
return (
<div
ref={containerRef}
className="size-full min-h-0 overflow-hidden px-2 py-1"
style={background ? { backgroundColor: background } : undefined}
/>
)
}
+2 -1
View File
@@ -18598,7 +18598,8 @@
"alreadyOpen": "Another Orca window already has a plain SSH terminal open to this host.",
"endedBySignal": "ssh was stopped (signal {{signal}}). Close this window when you’re done reading.",
"cancelled": "This plain SSH terminal was closed before it started.",
"terminalUnavailable": "Orca couldn’t open a terminal here."
"terminalUnavailable": "Orca couldn’t open a terminal here.",
"openHint": "Keeps you working on the host while Orca can’t set up there."
}
},
"i18n": {