diff --git a/mobile/src/worktree/worktree-host-context-labels.test.ts b/mobile/src/worktree/worktree-host-context-labels.test.ts index 365a5d652a4..a921ceebb90 100644 --- a/mobile/src/worktree/worktree-host-context-labels.test.ts +++ b/mobile/src/worktree/worktree-host-context-labels.test.ts @@ -146,4 +146,19 @@ describe('getWorktreeHostContextLabels', () => { expect(labeled.map((row) => row.hostContextLabel)).toEqual(['Local Mac', 'openclaw']) expect(labeled.map((row) => row.hostContextHostId)).toEqual(['local', sshHostId]) }) + + it('keeps labels distinct when legacy rows reuse an id across hosts', () => { + const rows = [ + worktree({ repoId: 'repo-local', worktreeId: 'same' }), + worktree({ repoId: 'repo-ssh', worktreeId: 'same' }) + ] + const labeled = applyWorktreeHostContextLabels(rows, { + ...sources, + repoHostIdByRepoId: buildRepoHostIdByRepoId([ + { id: 'repo-local' }, + { id: 'repo-ssh', connectionId: 'ssh-1785104650217-eduhep' } + ]) + }) + expect(labeled.map((row) => row.hostContextLabel)).toEqual(['Local Mac', 'openclaw']) + }) }) diff --git a/mobile/src/worktree/worktree-host-context-labels.ts b/mobile/src/worktree/worktree-host-context-labels.ts index bb7bab33962..de33c62ca5d 100644 --- a/mobile/src/worktree/worktree-host-context-labels.ts +++ b/mobile/src/worktree/worktree-host-context-labels.ts @@ -5,13 +5,13 @@ import { type ExecutionHostId } from '../../../src/shared/execution-host' import { getMixedHostContextLabels as getSharedMixedHostContextLabels } from '../../../src/shared/worktree/host-context-labels' +import { composeWorktreeHostIdentity } from '../../../src/shared/worktree/host-qualified-identity' export { buildHostLabelById, getHostContextLabel } from '../../../src/shared/worktree/host-context-labels' import type { RepoSummary } from './host-worktree-rpc-types' import type { Worktree } from './workspace-list-types' -import { getWorktreeRowIdentity } from './worktree-host-row-identity' export type HostLabelSources = { /** Host id per repo id from repo.list; rows from hosts that predate `hostId` fall back to it. */ @@ -39,6 +39,16 @@ export function resolveWorktreeHostId( ) } +function getResolvedWorktreeRowIdentity( + worktree: Pick, + repoHostIdByRepoId: ReadonlyMap +): string { + return composeWorktreeHostIdentity( + resolveWorktreeHostId(worktree, repoHostIdByRepoId), + worktree.worktreeId + ) +} + // Kept as a local adapter so existing mobile imports remain stable. /** @@ -51,7 +61,9 @@ export function getWorktreeHostContextLabels( ): Map | undefined { return getSharedMixedHostContextLabels(worktrees, { getHostId: (worktree) => resolveWorktreeHostId(worktree, sources.repoHostIdByRepoId), - getIdentity: getWorktreeRowIdentity, + // Legacy hosts omit row.hostId; key by the resolved repo owner so duplicate + // worktree ids from different hosts do not overwrite each other's label. + getIdentity: (worktree) => getResolvedWorktreeRowIdentity(worktree, sources.repoHostIdByRepoId), sources }) } @@ -65,7 +77,9 @@ export function applyWorktreeHostContextLabels( return worktrees } return worktrees.map((worktree) => { - const hostContextLabel = labels.get(getWorktreeRowIdentity(worktree)) + const hostContextLabel = labels.get( + getResolvedWorktreeRowIdentity(worktree, sources.repoHostIdByRepoId) + ) if (!hostContextLabel) { return worktree }