diff --git a/src/renderer/src/components/task-page-cache-selectors.test.ts b/src/renderer/src/components/task-page-cache-selectors.test.ts index ef1a0e20efee..81c1f1c139af 100644 --- a/src/renderer/src/components/task-page-cache-selectors.test.ts +++ b/src/renderer/src/components/task-page-cache-selectors.test.ts @@ -235,6 +235,38 @@ describe('task page cache selectors', () => { ).toBe(origin) }) + it('opens the copy the clicked twin host returned, not the other host cached copy', () => { + const local = { ...workItem('pr:7', 'twin'), repoHostId: 'local' as const } + const remote = { ...local, repoHostId: 'ssh:builder' as const } + const cache = { + local: entry([local]), + remote: entry([remote]) + } + + expect( + findTaskPageDialogWorkItem(cache, { + id: remote.id, + repoId: remote.repoId, + url: remote.url, + repoHostId: remote.repoHostId + }) + ).toBe(remote) + }) + + it('keeps each twin host row on its own copy when the cache syncs the table', () => { + const local = { ...workItem('pr:7', 'twin'), repoHostId: 'local' as const } + const remote = { ...local, repoHostId: 'ssh:builder' as const } + const pages = [[local, { ...remote }]] + + const nextPages = reconcileTaskPagePagesWithWorkItemsCache(pages, [ + entry([local]), + entry([remote]) + ]) + + expect(nextPages[0]?.map((item) => item.repoHostId)).toEqual(['local', 'ssh:builder']) + expect(nextPages[0]?.[1]).toBe(remote) + }) + it('reconciles paged table rows with patched work-item cache entries', () => { const stale = { ...workItem('pr-1', 'repo-1'), diff --git a/src/renderer/src/components/task-page-cache-selectors.ts b/src/renderer/src/components/task-page-cache-selectors.ts index 7cab1b34ece5..7885009c962f 100644 --- a/src/renderer/src/components/task-page-cache-selectors.ts +++ b/src/renderer/src/components/task-page-cache-selectors.ts @@ -29,6 +29,7 @@ export type TaskPageDialogWorkItemKey = { id: string repoId: string url: string + repoHostId?: GitHubWorkItem['repoHostId'] } | null export type TaskPageRepoSourceState = { @@ -121,10 +122,6 @@ export function selectTaskPageUnresolvedSourceRepos( return unresolved } -function taskPageWorkItemCacheKey(item: GitHubWorkItem): string { - return `${item.repoId}\u0000${item.id}` -} - export function reconcileTaskPagePagesWithWorkItemsCache( pages: TaskPageWorkItemPages, entries: readonly (CacheEntry | undefined)[] @@ -132,7 +129,7 @@ export function reconcileTaskPagePagesWithWorkItemsCache( const cachedItems = new Map() for (const entry of entries) { for (const item of entry?.data ?? []) { - cachedItems.set(taskPageWorkItemCacheKey(item), item) + cachedItems.set(taskPageWorkItemKey(item), item) } } @@ -143,7 +140,7 @@ export function reconcileTaskPagePagesWithWorkItemsCache( } let pageChanged = false const nextPage = page.map((item) => { - const cached = cachedItems.get(taskPageWorkItemCacheKey(item)) + const cached = cachedItems.get(taskPageWorkItemKey(item)) if (!cached || cached === item) { return item } @@ -244,7 +241,9 @@ export function findTaskPageDialogWorkItem( (wi) => wi.id === dialogWorkItemKey.id && wi.repoId === dialogWorkItemKey.repoId && - wi.url === dialogWorkItemKey.url + wi.url === dialogWorkItemKey.url && + // Why: a twin's other host caches the same PR; its copy routes edits to that host. + (!dialogWorkItemKey.repoHostId || wi.repoHostId === dialogWorkItemKey.repoHostId) ) if (found) { return found diff --git a/src/renderer/src/components/task-page-work-item-signatures.ts b/src/renderer/src/components/task-page-work-item-signatures.ts index 8abb2f07a192..9671d1e7fbc3 100644 --- a/src/renderer/src/components/task-page-work-item-signatures.ts +++ b/src/renderer/src/components/task-page-work-item-signatures.ts @@ -1,7 +1,8 @@ import type { GitHubWorkItem } from '../../../shared/github/work-item-types' +// Why the host: a twin's two hosts return the same PR under one repo id. export function taskPageWorkItemKey(item: GitHubWorkItem): string { - return `${item.repoId}\u0000${item.id}` + return `${item.repoId}\u0000${item.repoHostId ?? ''}\u0000${item.id}` } export function sortedStrings(values: readonly string[] | undefined): string { diff --git a/src/renderer/src/components/task-page/github/Rows.tsx b/src/renderer/src/components/task-page/github/Rows.tsx index 2597095f5ae6..e0d9a9bfe79d 100644 --- a/src/renderer/src/components/task-page/github/Rows.tsx +++ b/src/renderer/src/components/task-page/github/Rows.tsx @@ -1,4 +1,5 @@ import type { TaskPageComposerActionsModel } from '../../use-task-page-composer-actions' +import { getGitHubWorkItemRepo } from '@/lib/github-work-item-repo-row' import { findGithubWorkItemWorkspaceAttachment } from '@/lib/github-work-item-workspace-attachment' import { getWorktreeAttachmentLabel } from '@/lib/worktree-attachment-label' import { formatPRDelta } from '@/components/task-page-pr-delta-summary' @@ -48,7 +49,7 @@ export function TaskPageGitHubRows({ model: TaskPageComposerActionsModel }): React.JSX.Element | null { const { - repoMap, + repos, allWorktrees, selectedRepos, openGitHubDetailPage, @@ -65,7 +66,7 @@ export function TaskPageGitHubRows({
{!showGitHubTaskSkeletons && filteredWorkItems.map((item) => { - const itemRepo = repoMap.get(item.repoId) ?? null + const itemRepo = getGitHubWorkItemRepo(repos, item) ?? null const attachedWorkspace = findGithubWorkItemWorkspaceAttachment( allWorktrees, item.repoId, @@ -106,7 +107,7 @@ export function TaskPageGitHubRows({ // Why: clickable div not a