diff --git a/notes/host-context-implementation-checklist.md b/notes/host-context-implementation-checklist.md index 2a315e72086..5a3e7324bbb 100644 --- a/notes/host-context-implementation-checklist.md +++ b/notes/host-context-implementation-checklist.md @@ -284,3 +284,4 @@ Done means: reviewers and future agents can understand the vision, implementatio - [x] 2026-06-13: Extended Tasks source availability reasons so missing provider auth, unavailable source tooling, and unsupported providers can surface as distinct source-host states instead of generic connection/version failures. The checklist row remains partial until runtime/provider preflight feeds these reasons end to end. Verified with focused source-summary tests and targeted oxlint. - [x] 2026-06-13: Fed desktop-owned GitHub/GitLab preflight into Tasks source availability for local and SSH project sources, so missing `gh`/`glab` or missing CLI auth disables the affected provider with source-tool/auth-specific copy. Runtime-owned sources intentionally skip desktop preflight and continue to rely on runtime capability/fetch ownership. Verified with provider availability tests, Tasks source summary tests, TaskPage source-boundary tests, targeted oxlint, and full `pnpm run typecheck`. - [x] 2026-06-13: Electron-verified a local project with local GitHub credentials: added the local Orca worktree to the running dev app, confirmed `gh` auth for `Jinwoo-H`, fetched 10 GitHub work items through explicit `TaskSourceContext { provider: github, hostId: local }`, verified the local source-scoped cache key, and captured the rendered Tasks page showing GitHub issues for the local project. +- [x] 2026-06-13: Snapshotted Source Control PR-generation runtime ownership and reused that owner for generation, cancellation, and post-generation git refresh, preventing async PR generation from following a later focused-host switch. Verified with focused Source Control host-boundary tests, targeted oxlint, and `git diff --check`. diff --git a/src/renderer/src/components/right-sidebar/SourceControl.host-context-boundary.test.ts b/src/renderer/src/components/right-sidebar/SourceControl.host-context-boundary.test.ts new file mode 100644 index 00000000000..a5a8244b437 --- /dev/null +++ b/src/renderer/src/components/right-sidebar/SourceControl.host-context-boundary.test.ts @@ -0,0 +1,40 @@ +import { readFileSync } from 'node:fs' +import { join } from 'node:path' +import { describe, expect, it } from 'vitest' + +const SOURCE_CONTROL_SOURCE = readFileSync(join(__dirname, 'SourceControl.tsx'), 'utf8') + +function sourceBetween(source: string, startPattern: string, endPattern: string): string { + const start = source.indexOf(startPattern) + expect(start).toBeGreaterThanOrEqual(0) + const end = source.indexOf(endPattern, start + startPattern.length) + expect(end).toBeGreaterThan(start) + return source.slice(start, end) +} + +describe('SourceControl host-context boundaries', () => { + it('snapshots PR generation host ownership and reuses it after async branch preparation', () => { + const generateSection = sourceBetween( + SOURCE_CONTROL_SOURCE, + 'const handleGeneratePullRequestFieldsForActive = useCallback(', + 'const handleCancelGeneratePullRequestFieldsForActive = useCallback(' + ) + expect(generateSection).toContain('runtimeTargetSettings: activeRepoSettings') + expect(generateSection).toContain('settings: context.runtimeTargetSettings') + + const cancelSection = sourceBetween( + SOURCE_CONTROL_SOURCE, + 'const handleCancelGeneratePullRequestFieldsForActive = useCallback(', + 'const {' + ) + expect(cancelSection).toContain('settings: record.context.runtimeTargetSettings') + + const refreshSection = sourceBetween( + SOURCE_CONTROL_SOURCE, + 'const refreshGitStatusAfterPullRequestGeneration = useCallback(', + 'useEffect(() => {' + ) + expect(refreshSection).toContain('settings: context.runtimeTargetSettings') + expect(refreshSection).not.toContain('settings: activeRepoSettings') + }) +}) diff --git a/src/renderer/src/components/right-sidebar/SourceControl.tsx b/src/renderer/src/components/right-sidebar/SourceControl.tsx index aba828f247e..12f4c91313d 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.tsx @@ -969,8 +969,9 @@ function SourceControlInner(): React.JSX.Element { } try { await refreshGitStatusForWorktree({ - // Why: route git status by the repo OWNER host, not the focused runtime. - settings: activeRepoSettings, + // Why: generation can finish after the user switches hosts; refresh + // the same host that owned the generation request. + settings: context.runtimeTargetSettings, worktreeId: context.worktreeId, worktreePath: context.worktreePath, connectionId: context.connectionId, @@ -987,7 +988,6 @@ function SourceControlInner(): React.JSX.Element { } }, [ - activeRepoSettings, fetchUpstreamStatus, isFolder, setGitStatus, @@ -1953,7 +1953,8 @@ function SourceControlInner(): React.JSX.Element { connectionId: getConnectionId(activeWorktreeId) ?? undefined, requestId, repoId: activeRepo.id, - branch: branchName + branch: branchName, + runtimeTargetSettings: activeRepoSettings } const seed = { ...fields } // Why: SourceControl can unmount on tab switches; persisting the running @@ -1967,7 +1968,7 @@ function SourceControlInner(): React.JSX.Element { const result = await generateRuntimePullRequestFields( { // Why: route generation by the repo OWNER host, not the focused runtime. - settings: activeRepoSettings, + settings: context.runtimeTargetSettings, worktreeId: context.worktreeId, worktreePath: context.worktreePath, connectionId: context.connectionId @@ -2050,8 +2051,9 @@ function SourceControlInner(): React.JSX.Element { return resolvePullRequestGenerationCancel(current) }) void cancelRuntimeGeneratePullRequestFields({ - // Why: route the cancel by the repo OWNER host, not the focused runtime. - settings: activeRepoSettings, + // Why: the user can switch hosts while generation runs; cancel the + // original request owner instead of the current focused host. + settings: record.context.runtimeTargetSettings, worktreeId: record.context.worktreeId, worktreePath: record.context.worktreePath, connectionId: record.context.connectionId @@ -2068,12 +2070,7 @@ function SourceControlInner(): React.JSX.Element { } }) }) - }, [ - activeRepoSettings, - activePullRequestGenerationKey, - prGenerationRecords, - updatePullRequestGenerationRecord - ]) + }, [activePullRequestGenerationKey, prGenerationRecords, updatePullRequestGenerationRecord]) const { aiGenerationEnabled: prAiGenerationEnabled,