diff --git a/notes/host-context-implementation-checklist.md b/notes/host-context-implementation-checklist.md index 6941c897786..6815ea0b46c 100644 --- a/notes/host-context-implementation-checklist.md +++ b/notes/host-context-implementation-checklist.md @@ -219,6 +219,7 @@ Done means: reviewers and future agents can understand the vision, implementatio - [x] 2026-06-12: Added a narrow `.gitignore` rule for `notes/artifacts/` so verification screenshots stay local while the HTML report can reference them; verified with `git check-ignore`. - [x] 2026-06-12: Added `taskSourceContext` and `workspaceRunContext` to retryable pending worktree creation requests, seeded them from the Composer's selected task source and project-host setup, and verified the pending store preserves both identities with focused worktree tests plus web typecheck. - [x] 2026-06-12: Threaded GitHub Tasks row-level mutations through `TaskSourceContext` for issue state, assignees, reviewer requests/removals, PR merge/auto-merge, and new issue creation/follow-up reads; verified with focused GitHub/TaskPage tests, focused oxlint, `pnpm run typecheck`, and `git diff --check`. +- [x] 2026-06-12: Added provider account-scope diagnostics to GitHub/GitLab CLI integration cards so code-host credential checks show whether they belong to the local desktop client or selected remote server, matching the existing Linear/Jira ownership copy; verified with focused integration-card scope tests and oxlint. - [x] 2026-06-12: Updated PR #5071 with the current verified scope, evidence docs, validation commands, explicit out-of-scope cloud/onboarding items, and remaining provider/version-skew risk. - [x] 2026-06-12: Added load-time backfill for legacy automations and automation runs that predate `runContext`/`sourceContext`, deriving explicit project host and provider source contexts from the saved repo id/path; verified with focused persistence migration coverage, automation service/precheck tests, daemon/settings merge-repair tests, and full `pnpm run typecheck`. - [x] 2026-06-12: Repaired the merge-repair lint gate without disabling max-lines by extracting automation run-target/usage modules and settings subcomponents, fixed stale source-context hook dependencies, and tightened GitHub PR checks cache ownership so `noCache` bypasses cache/in-flight requests while head-specific checks are reused during refresh events; verified targeted oxlint, focused automation/settings/GitHub cache tests (10 files / 157 tests), `github-checks-cache.test.ts`, `git diff --check`, and full `pnpm run typecheck`. diff --git a/src/renderer/src/components/settings/cli-source-control-integration-cards.test.tsx b/src/renderer/src/components/settings/cli-source-control-integration-cards.test.tsx new file mode 100644 index 00000000000..cbfe5db6bfc --- /dev/null +++ b/src/renderer/src/components/settings/cli-source-control-integration-cards.test.tsx @@ -0,0 +1,102 @@ +// @vitest-environment happy-dom + +import { act } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { + GitHubIntegrationCard, + GitLabIntegrationCard +} from './cli-source-control-integration-cards' + +type StoreState = { + settings: { activeRuntimeEnvironmentId: string | null } +} + +const mocks = vi.hoisted(() => ({ + store: { current: null as StoreState | null }, + preflight: { + statuses: { + ghStatus: 'connected', + glabStatus: 'connected', + bitbucketStatus: 'not-configured', + azureDevOpsStatus: 'not-configured', + giteaStatus: 'not-configured', + bitbucketAccount: null, + azureDevOpsAccount: null, + giteaAccount: null + }, + unavailable: false, + refresh: vi.fn() + } +})) + +vi.mock('@/store', () => ({ + useAppStore: (selector: (state: StoreState) => unknown) => { + if (!mocks.store.current) { + throw new Error('Store state was not installed') + } + return selector(mocks.store.current) + } +})) + +vi.mock('./source-control-preflight-card-status', () => ({ + usePreflightCardStatuses: () => mocks.preflight +})) + +let root: Root | null = null +let container: HTMLDivElement | null = null + +async function renderCard(card: React.ReactNode): Promise { + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + await act(async () => { + root?.render(card) + }) + return container +} + +describe('CLI source-control integration card account scope', () => { + afterEach(async () => { + if (root) { + await act(async () => { + root?.unmount() + }) + } + root = null + container?.remove() + container = null + mocks.store.current = null + mocks.preflight.statuses.ghStatus = 'connected' + mocks.preflight.statuses.glabStatus = 'connected' + mocks.preflight.unavailable = false + mocks.preflight.refresh.mockClear() + }) + + it('shows local-client ownership for connected GitHub CLI credentials', async () => { + mocks.store.current = { settings: { activeRuntimeEnvironmentId: null } } + + const rendered = await renderCard() + + expect(rendered.textContent).toContain('GitHub') + expect(rendered.textContent).toContain('Connected') + expect(rendered.textContent).toContain('Account scope: Local Mac') + expect(rendered.textContent).toContain( + 'Credentials and account checks for this provider are owned by this desktop client.' + ) + }) + + it('shows remote-server ownership for GitLab CLI credential checks', async () => { + mocks.store.current = { settings: { activeRuntimeEnvironmentId: 'runtime-1' } } + mocks.preflight.statuses.glabStatus = 'not-authenticated' + + const rendered = await renderCard() + + expect(rendered.textContent).toContain('GitLab') + expect(rendered.textContent).toContain('Account scope: Remote server: runtime-1') + expect(rendered.textContent).toContain( + 'Credentials and account checks for this provider are owned by this remote server.' + ) + expect(rendered.textContent).toContain('glab auth login') + }) +}) diff --git a/src/renderer/src/components/settings/cli-source-control-integration-cards.tsx b/src/renderer/src/components/settings/cli-source-control-integration-cards.tsx index 33332c451f8..3c3508d04ea 100644 --- a/src/renderer/src/components/settings/cli-source-control-integration-cards.tsx +++ b/src/renderer/src/components/settings/cli-source-control-integration-cards.tsx @@ -1,9 +1,36 @@ import { ExternalLink, Github, Gitlab, Terminal } from 'lucide-react' import { Button } from '@/components/ui/button' +import { useAppStore } from '@/store' import { IntegrationCardDetails, IntegrationCardShell } from './integration-card-shell' +import { getProviderAccountScope } from './provider-account-scope' import { usePreflightCardStatuses } from './source-control-preflight-card-status' import { translate } from '@/i18n/i18n' +function ProviderAccountScopeDetails({ + children +}: { + children?: React.ReactNode +}): React.JSX.Element { + const settings = useAppStore((s) => s.settings) + const accountScope = getProviderAccountScope(settings) + + return ( + +
+ + {translate( + 'auto.components.settings.cli.source.control.integration.cards.account_scope', + 'Account scope: {{value0}}', + { value0: accountScope.label } + )} + +
{accountScope.description}
+
+ {children} +
+ ) +} + export function GitHubIntegrationCard(): React.JSX.Element { const { statuses, unavailable, refresh } = usePreflightCardStatuses('gh') const status = unavailable ? 'unavailable' : statuses.ghStatus @@ -43,9 +70,9 @@ export function GitHubIntegrationCard(): React.JSX.Element { : 'Not authenticated' } > - {status !== 'checking' && !connected ? ( - - {status === 'unavailable' ? ( + + {status !== 'checking' && !connected ? ( + status === 'unavailable' ? ( <>

{translate( @@ -125,9 +152,9 @@ export function GitHubIntegrationCard(): React.JSX.Element { - )} - - ) : null} + ) + ) : null} + ) } @@ -171,9 +198,9 @@ export function GitLabIntegrationCard(): React.JSX.Element { : 'Not authenticated' } > - {status !== 'checking' && !connected ? ( - - {status === 'unavailable' ? ( + + {status !== 'checking' && !connected ? ( + status === 'unavailable' ? ( <>

{translate( @@ -257,9 +284,9 @@ export function GitLabIntegrationCard(): React.JSX.Element { - )} - - ) : null} + ) + ) : null} + ) }