refactor(mobile): export the New Workspace SSH target key

Names an SSH target by its holding host and id, so other server-aware screens can key their SSH
state the same way.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
Jinwoo-H
2026-10-11 22:58:15 -04:00
parent e5eb4b5668
commit a2bb1cf8a5
3 changed files with 31 additions and 3 deletions
@@ -9,6 +9,7 @@ import {
} from '../tasks/mobile-workspace-source-operations'
import {
deriveWorkspaceSshGate,
workspaceSshTargetKey,
type WorkspaceSshGate,
type WorkspaceSshRecord
} from '../tasks/workspace-ssh-gate'
@@ -43,8 +44,7 @@ export function useNewWorkspaceExecutionTarget(args: {
connect: () => Promise<void>
} {
const { client, connectionId, server, visible } = args
// Why the host too: the desktop and a server can each hold a target with the same id.
const target = JSON.stringify([server ?? null, connectionId])
const target = workspaceSshTargetKey(server, connectionId)
const [sshState, setSshEntry] = useState<SshStateEntry | null>(null)
const setSshState = (record: WorkspaceSshRecord) => setSshEntry({ target, record })
// A connect reply lands only while its target is still the one shown, so it never overwrites another's.
+17 -1
View File
@@ -1,7 +1,23 @@
import { describe, expect, it } from 'vitest'
import { deriveWorkspaceSshGate, workspaceSshStatusLabel } from './workspace-ssh-gate'
import {
deriveWorkspaceSshGate,
workspaceSshStatusLabel,
workspaceSshTargetKey
} from './workspace-ssh-gate'
describe('workspace SSH gate', () => {
it('keys a target by the host holding it, so same-named targets on two hosts differ', () => {
expect(workspaceSshTargetKey(undefined, 'devbox')).not.toBe(
workspaceSshTargetKey('runtime:env-1', 'devbox')
)
expect(workspaceSshTargetKey('runtime:env-1', 'devbox')).not.toBe(
workspaceSshTargetKey('runtime:env-2', 'devbox')
)
expect(workspaceSshTargetKey('runtime:env-1', 'devbox')).toBe(
workspaceSshTargetKey('runtime:env-1', 'devbox')
)
})
it('does not gate local repositories', () => {
expect(
deriveWorkspaceSshGate({
+12
View File
@@ -1,3 +1,4 @@
import type { ExecutionHostId } from '../../../src/shared/execution-host'
import type { SshConnectionState, SshConnectionStatus } from '../../../src/shared/ssh-types'
/**
@@ -14,6 +15,17 @@ export type WorkspaceSshRecord = Omit<SshConnectionState, 'error' | 'reconnectAt
reconnectAttempt?: number
}
/**
* Names an SSH target by the host holding it as well as its id: the desktop and a server can each
* hold a target with the same id. `server` is undefined for the desktop's own targets.
*/
export function workspaceSshTargetKey(
server: ExecutionHostId | undefined,
connectionId: string | null
): string {
return JSON.stringify([server ?? null, connectionId])
}
export type WorkspaceSshGate = {
status: SshConnectionStatus | null
requiresConnection: boolean