fix: resolve terminal surface host from the runtime owner when no physical host is published

hasRenderableTerminalWorktreeSurface returned false whenever the route had no
executionHostId, which is the normal shape for a paired-HUB row that only
carries runtimeOwnerEnvironmentId. Focused creates and reveals on such rows
would fail closed with worktree_hidden even though the sidebar row exists.
Share one catalog-host resolver between the surface check and the import path.
This commit is contained in:
Jinwoo-H
2026-09-06 22:08:23 -04:00
parent feed851f45
commit 6f08d48a3c
3 changed files with 37 additions and 13 deletions
@@ -1,6 +1,8 @@
import { hasRenderableTerminalWorktreeSurface as hasTerminalWorktreeRow } from '@/lib/terminal-worktree-route'
import {
hasRenderableTerminalWorktreeSurface as hasTerminalWorktreeRow,
resolveTerminalWorktreeCatalogHostId
} from '@/lib/terminal-worktree-route'
import { importNewExternalWorktreeInboxPaths } from '@/components/sidebar/new-external-worktrees-inbox-actions'
import { resolveWorktreeOperationRoute } from '@/lib/worktree-operation-route'
import { findRepoForHost, getRepoHostIdentity } from '@/store/slices/repo-host-identity'
import { getRepoIdFromWorktreeId } from '@/store/slices/worktree-helpers'
import {
@@ -22,10 +24,7 @@ export async function ensureTerminalWorktreeVisible(worktreeId: string): Promise
if (hasTerminalWorktreeRow(state, worktreeId)) {
return
}
const route = resolveWorktreeOperationRoute(state, worktreeId)
const hostId = route?.runtimeEnvironmentId
? (`runtime:${encodeURIComponent(route.runtimeEnvironmentId)}` as const)
: route?.executionHostId
const hostId = resolveTerminalWorktreeCatalogHostId(state, worktreeId)
if (!hostId) {
throw hiddenTerminalWorktreeError()
}
@@ -1,6 +1,10 @@
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants'
import { isEphemeralSetupTerminalWorktreeId } from '../../../shared/ephemeral-setup-terminal-worktree-id'
import { parseExecutionHostId } from '../../../shared/execution-host'
import {
parseExecutionHostId,
toRuntimeExecutionHostId,
type ExecutionHostId
} from '../../../shared/execution-host'
import { parseWorkspaceKey } from '../../../shared/workspace-scope'
import type { AppState } from '@/store/types'
import { getIndexedWorktreesById } from '@/store/worktree-repo-index'
@@ -24,6 +28,21 @@ export type TerminalWorktreeRoute = {
runtimeEnvironmentId: string | null
}
/** The catalog host whose rows can render this worktree, or null when ownership is unresolved. */
export function resolveTerminalWorktreeCatalogHostId(
state: AppState,
worktreeId: string
): ExecutionHostId | null {
const route = resolveWorktreeOperationRoute(state, worktreeId)
if (!route) {
return null
}
// Why: a paired-HUB row can publish only its environment owner, leaving the physical host null.
return route.runtimeEnvironmentId
? toRuntimeExecutionHostId(route.runtimeEnvironmentId)
: route.executionHostId
}
export function hasRenderableTerminalWorktreeSurface(
state: AppState,
worktreeId: string | null | undefined
@@ -41,15 +60,11 @@ export function hasRenderableTerminalWorktreeSurface(
)
}
// Only the workbench's row index can host new tabs; detected rows and inline setup ids cannot.
const route = resolveWorktreeOperationRoute(state, worktreeId)
const hostId = route?.executionHostId
if (!hostId) {
const catalogHostId = resolveTerminalWorktreeCatalogHostId(state, worktreeId)
if (!catalogHostId) {
return false
}
const repoId = getRepoIdFromWorktreeId(worktreeId)
const catalogHostId = route.runtimeEnvironmentId
? (`runtime:${encodeURIComponent(route.runtimeEnvironmentId)}` as const)
: hostId
const matchOptions = worktreeHostMatchOptions(state, repoId, catalogHostId)
return state.worktreesByRepo
? getIndexedWorktreesById(state.worktreesByRepo, worktreeId).some((row) =>
@@ -28,6 +28,16 @@ describe('hasRenderableTerminalWorktreeSurface', () => {
}
})
it('accepts a paired-runtime row that carries only its environment owner', () => {
const store = state({
repos: [{ id: 'repo-1', executionHostId: 'runtime:hub' }],
worktreesByRepo: {
'repo-1': [{ id: 'repo-1::/workspace', repoId: 'repo-1', runtimeOwnerEnvironmentId: 'hub' }]
}
} as unknown as Partial<AppState>)
expect(hasRenderableTerminalWorktreeSurface(store, 'repo-1::/workspace')).toBe(true)
})
it('rejects a hidden detected worktree even when its repo is registered', () => {
const store = state({
detectedWorktreesByRepo: {