From bf55667eaa3a91ed5ce154868f96b253ee502ff8 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Fri, 12 Jun 2026 20:28:00 -0700 Subject: [PATCH] Route GitHub task row actions by source context Co-authored-by: Orca --- .../host-context-implementation-checklist.md | 1 + src/main/ipc/github-work-item-args.ts | 4 + src/main/ipc/github.ts | 19 +- src/preload/api-types.ts | 2 + src/preload/index.ts | 2 + src/renderer/src/components/TaskPage.tsx | 209 ++++++++++++++---- ...ture-interaction-writer-boundaries.test.ts | 26 ++- 7 files changed, 214 insertions(+), 49 deletions(-) diff --git a/notes/host-context-implementation-checklist.md b/notes/host-context-implementation-checklist.md index bfc920ea9fd..bd1154a4c0c 100644 --- a/notes/host-context-implementation-checklist.md +++ b/notes/host-context-implementation-checklist.md @@ -218,6 +218,7 @@ Done means: reviewers and future agents can understand the vision, implementatio - [x] 2026-06-12: Polished sidebar host headers from card-like rows into quieter sidebar grouping rows while keeping explicit SSH/status/count text; verified with focused sidebar tests, web typecheck, and live Electron screenshot `notes/artifacts/host-context-sidebar-calm-host-headers.png`. - [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: 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/main/ipc/github-work-item-args.ts b/src/main/ipc/github-work-item-args.ts index c0c641f262e..44c689b3aef 100644 --- a/src/main/ipc/github-work-item-args.ts +++ b/src/main/ipc/github-work-item-args.ts @@ -1,5 +1,9 @@ +import type { TaskSourceContext } from '../../shared/task-source-context' + export type WorkItemArgs = { repoPath: string + repoId?: string | null + sourceContext?: TaskSourceContext | null number: number type?: 'issue' | 'pr' } diff --git a/src/main/ipc/github.ts b/src/main/ipc/github.ts index 2551585bd0c..87acce471ef 100644 --- a/src/main/ipc/github.ts +++ b/src/main/ipc/github.ts @@ -319,7 +319,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi ipcMain.handle( 'gh:createIssue', - (_event, args: { repoPath: string; title: string; body: string } & GitHubCreateIssueFields) => { + (_event, args: RepoScopedArgs & { title: string; body: string } & GitHubCreateIssueFields) => { const repo = assertRegisteredRepo(args, store) const fields = args.labels !== undefined || args.assignees !== undefined @@ -727,6 +727,8 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi event, args: { repoPath: string + repoId?: string | null + sourceContext?: TaskSourceContext | null prNumber: number method?: 'merge' | 'squash' | 'rebase' prRepo?: GitHubOwnerRepo | null @@ -756,6 +758,8 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi event, args: { repoPath: string + repoId?: string | null + sourceContext?: TaskSourceContext | null prNumber: number enabled: boolean prRepo?: GitHubOwnerRepo | null @@ -783,7 +787,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi 'gh:updatePRState', async ( event, - args: { repoPath: string; prNumber: number; updates: GitHubPullRequestStateUpdate } + args: RepoScopedArgs & { prNumber: number; updates: GitHubPullRequestStateUpdate } ) => { const repo = assertRegisteredRepo(args, store) if ( @@ -813,7 +817,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi 'gh:rerunPRChecks', async ( _event, - args: { repoPath: string; prNumber: number; headSha?: string; failedOnly?: boolean } + args: RepoScopedArgs & { prNumber: number; headSha?: string; failedOnly?: boolean } ) => { const repo = assertRegisteredRepo(args, store) if ( @@ -834,7 +838,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi ipcMain.handle( 'gh:requestPRReviewers', - async (event, args: { repoPath: string; prNumber: number; reviewers: string[] }) => { + async (event, args: RepoScopedArgs & { prNumber: number; reviewers: string[] }) => { const repo = assertRegisteredRepo(args, store) const result = await requestPRReviewers( repo.path, @@ -854,7 +858,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi ipcMain.handle( 'gh:removePRReviewers', - async (event, args: { repoPath: string; prNumber: number; reviewers: string[] }) => { + async (event, args: RepoScopedArgs & { prNumber: number; reviewers: string[] }) => { const repo = assertRegisteredRepo(args, store) const result = await removePRReviewers( repo.path, @@ -874,10 +878,7 @@ export function registerGitHubHandlers(store: Store, stats: StatsCollector): voi ipcMain.handle( 'gh:updateIssue', - async ( - event, - args: { repoPath: string; repoId?: string; number: number; updates: GitHubIssueUpdate } - ) => { + async (event, args: RepoScopedArgs & { number: number; updates: GitHubIssueUpdate }) => { const repo = assertRegisteredRepo(args, store) if (typeof args.number !== 'number' || !Number.isInteger(args.number) || args.number < 1) { return { ok: false, error: 'Invalid issue number' } diff --git a/src/preload/api-types.ts b/src/preload/api-types.ts index faee76224d4..92f513fe538 100644 --- a/src/preload/api-types.ts +++ b/src/preload/api-types.ts @@ -1142,6 +1142,7 @@ export type PreloadApi = { workItem: (args: { repoPath: string repoId?: string + sourceContext?: TaskSourceContext | null number: number type?: 'issue' | 'pr' }) => Promise | null> @@ -1177,6 +1178,7 @@ export type PreloadApi = { createIssue: (args: { repoPath: string repoId?: string + sourceContext?: TaskSourceContext | null title: string body: string labels?: string[] diff --git a/src/preload/index.ts b/src/preload/index.ts index 9d9eba83bc7..a9c3712de79 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -950,6 +950,7 @@ const api = { workItem: (args: { repoPath: string repoId?: string + sourceContext?: TaskSourceContext | null number: number type?: 'issue' | 'pr' }): Promise => ipcRenderer.invoke('gh:workItem', args), @@ -989,6 +990,7 @@ const api = { createIssue: (args: { repoPath: string repoId?: string + sourceContext?: TaskSourceContext | null title: string body: string labels?: string[] diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index 9d7f70bcf0a..5531540e886 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -148,6 +148,7 @@ import { getRepoExecutionHostId } from '../../../shared/execution-host' import { projectHostSetupProjectionFromRepos } from '../../../shared/project-host-setup-projection' import { getTaskSourceCacheScope, + getTaskSourceRuntimeSettings, normalizeTaskSourceContext, type TaskSourceContext } from '../../../shared/task-source-context' @@ -897,15 +898,27 @@ function buildJiraCreateCustomFields( function GHStatusCell({ item, - repo + repo, + sourceContext }: { item: GitHubWorkItem repo: Repo | null + sourceContext?: TaskSourceContext | null }): React.JSX.Element { const patchWorkItem = useAppStore((s) => s.patchWorkItem) const repoOwnerSettings = useAppStore( useShallow((s) => getSettingsForRepoRuntimeOwner(s, repo?.id ?? null)) ) + const sourceSettings = useMemo( + () => + sourceContext?.provider === 'github' + ? ({ + ...repoOwnerSettings, + ...getTaskSourceRuntimeSettings(sourceContext) + } as typeof repoOwnerSettings) + : repoOwnerSettings, + [repoOwnerSettings, sourceContext] + ) const [statusStateDraft, setStatusStateDraft] = useState(() => createTaskPageGitHubStatusStateDraft(item) ) @@ -937,18 +950,21 @@ function GHStatusCell({ const reqId = reqRef.current updateLocalState(newState) patchWorkItem(item.id, { state: newState }, item.repoId) - const target = getActiveRuntimeTarget(repoOwnerSettings) + const target = getActiveRuntimeTarget(sourceSettings) + const runtimeRepoId = + sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id const updatePromise = target.kind === 'environment' ? callRuntimeRpc<{ ok?: boolean; error?: string }>( target, 'github.updateIssue', - { repo: repo.id, number: item.number, updates: { state: newState } }, + { repo: runtimeRepoId, number: item.number, updates: { state: newState } }, { timeoutMs: 30_000 } ) : window.api.gh.updateIssue({ repoPath: repo.path, repoId: repo.id, + sourceContext, number: item.number, updates: { state: newState } }) @@ -982,7 +998,7 @@ function GHStatusCell({ toast.error(translate('auto.components.TaskPage.1c893195ac', 'Failed to update state')) }) }, - [item, localState, patchWorkItem, repo, repoOwnerSettings, updateLocalState] + [item, localState, patchWorkItem, repo, sourceContext, sourceSettings, updateLocalState] ) if (item.type !== 'issue' || !repo) { @@ -1331,15 +1347,27 @@ function GitHubIssueAssigneeSelector({ function GHAssigneesCell({ item, - repo + repo, + sourceContext }: { item: GitHubWorkItem repo: Repo | null + sourceContext?: TaskSourceContext | null }): React.JSX.Element { const patchWorkItem = useAppStore((s) => s.patchWorkItem) const repoOwnerSettings = useAppStore( useShallow((s) => getSettingsForRepoRuntimeOwner(s, repo?.id ?? null)) ) + const sourceSettings = useMemo( + () => + sourceContext?.provider === 'github' + ? ({ + ...repoOwnerSettings, + ...getTaskSourceRuntimeSettings(sourceContext) + } as typeof repoOwnerSettings) + : repoOwnerSettings, + [repoOwnerSettings, sourceContext] + ) const [open, setOpen] = useState(false) const [pendingLogin, setPendingLogin] = useState(null) const assignees = useMemo(() => item.assignees ?? [], [item.assignees]) @@ -1358,7 +1386,7 @@ function GHAssigneesCell({ open ? owner : null, open ? repoName : null, seedLogins, - repoOwnerSettings + sourceSettings ) const toggleAssignee = useCallback( @@ -1377,7 +1405,7 @@ function GHAssigneesCell({ try { const updates = isOn ? { removeAssignees: [user.login] } : { addAssignees: [user.login] } - const target = getActiveRuntimeTarget(repoOwnerSettings) + const target = getActiveRuntimeTarget(sourceSettings) if (owner && repoName) { const args = { owner, @@ -1398,17 +1426,20 @@ function GHAssigneesCell({ throw new Error(res.error.message) } } else if (repo) { + const runtimeRepoId = + sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id const res = target.kind === 'environment' ? await callRuntimeRpc<{ ok?: boolean; error?: string }>( target, 'github.updateIssue', - { repo: repo.id, number: item.number, updates }, + { repo: runtimeRepoId, number: item.number, updates }, { timeoutMs: 30_000 } ) : await window.api.gh.updateIssue({ repoPath: repo.path, repoId: repo.id, + sourceContext, number: item.number, updates }) @@ -1441,7 +1472,8 @@ function GHAssigneesCell({ pendingLogin, repo, repoName, - repoOwnerSettings + sourceContext, + sourceSettings ] ) @@ -1646,10 +1678,12 @@ function buildRequestedReviewUsers( function PRReviewCell({ item, - repo + repo, + sourceContext }: { item: GitHubWorkItem repo: Repo | null + sourceContext?: TaskSourceContext | null }): React.JSX.Element { const [open, setOpen] = useState(false) const [reviewerInput, setReviewerInput] = useState('') @@ -1664,10 +1698,19 @@ function PRReviewCell({ const patchWorkItem = useAppStore((s) => s.patchWorkItem) const [activeReviewerCursor, setActiveReviewerCursor] = useState({ resetKey: '', index: 0 }) const [submitting, setSubmitting] = useState(false) - const settings = useAppStore((s) => s.settings) const repoOwnerSettings = useAppStore( useShallow((s) => getSettingsForRepoRuntimeOwner(s, repo?.id ?? null)) ) + const sourceSettings = useMemo( + () => + sourceContext?.provider === 'github' + ? ({ + ...repoOwnerSettings, + ...getTaskSourceRuntimeSettings(sourceContext) + } as typeof repoOwnerSettings) + : repoOwnerSettings, + [repoOwnerSettings, sourceContext] + ) const reviewerInputRef = useRef(null) const reviewerInputFocusFrameRef = useRef(null) @@ -1734,7 +1777,7 @@ function PRReviewCell({ open && reviewSlug ? reviewSlug.owner : null, open && reviewSlug ? reviewSlug.repo : null, reviewerSeedUsers.map((user) => user.login), - settings + sourceSettings ) const authorLogin = item.author?.toLowerCase() ?? null @@ -1862,18 +1905,21 @@ function PRReviewCell({ } setSubmitting(true) try { - const target = getActiveRuntimeTarget(repoOwnerSettings) + const target = getActiveRuntimeTarget(sourceSettings) + const runtimeRepoId = + sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id const result = target.kind === 'environment' ? await callRuntimeRpc<{ ok: boolean; error?: string }>( target, 'github.requestPRReviewers', - { repo: repo.id, prNumber: item.number, reviewers: logins }, + { repo: runtimeRepoId, prNumber: item.number, reviewers: logins }, { timeoutMs: 30_000 } ) : await window.api.gh.requestPRReviewers({ repoPath: repo.path, repoId: repo.id, + sourceContext, prNumber: item.number, reviewers: logins }) @@ -1911,18 +1957,21 @@ function PRReviewCell({ } setSubmitting(true) try { - const target = getActiveRuntimeTarget(repoOwnerSettings) + const target = getActiveRuntimeTarget(sourceSettings) + const runtimeRepoId = + sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id const result = target.kind === 'environment' ? await callRuntimeRpc<{ ok: boolean; error?: string }>( target, 'github.removePRReviewers', - { repo: repo.id, prNumber: item.number, reviewers: logins }, + { repo: runtimeRepoId, prNumber: item.number, reviewers: logins }, { timeoutMs: 30_000 } ) : await window.api.gh.removePRReviewers({ repoPath: repo.path, repoId: repo.id, + sourceContext, prNumber: item.number, reviewers: logins }) @@ -2226,14 +2275,29 @@ function PRChecksCell({ function PRMergeCell({ item, repo, + sourceContext, onRefresh }: { item: GitHubWorkItem repo: Repo | null + sourceContext?: TaskSourceContext | null onRefresh: () => void }): React.JSX.Element { const [merging, setMerging] = useState(false) const confirm = useConfirmationDialog() + const repoOwnerSettings = useAppStore( + useShallow((s) => getSettingsForRepoRuntimeOwner(s, repo?.id ?? null)) + ) + const sourceSettings = useMemo( + () => + sourceContext?.provider === 'github' + ? ({ + ...repoOwnerSettings, + ...getTaskSourceRuntimeSettings(sourceContext) + } as typeof repoOwnerSettings) + : repoOwnerSettings, + [repoOwnerSettings, sourceContext] + ) if (item.type !== 'pr') { return ( @@ -2266,13 +2330,30 @@ function PRMergeCell({ } setMerging(true) try { - const result = await window.api.gh.mergePR({ - repoPath: repo.path, - repoId: repo.id, - prNumber: item.number, - method, - prRepo: item.prRepo ?? null - }) + const target = getActiveRuntimeTarget(sourceSettings) + const runtimeRepoId = + sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id + const result = + target.kind === 'environment' + ? await callRuntimeRpc<{ ok: boolean; error?: string }>( + target, + 'github.mergePR', + { + repo: runtimeRepoId, + prNumber: item.number, + method, + prRepo: item.prRepo ?? null + }, + { timeoutMs: 30_000 } + ) + : await window.api.gh.mergePR({ + repoPath: repo.path, + repoId: repo.id, + sourceContext, + prNumber: item.number, + method, + prRepo: item.prRepo ?? null + }) if (result.ok) { useAppStore.getState().recordFeatureInteraction('github-tasks') toast.success(translate('auto.components.TaskPage.a161925adc', 'Pull request merged')) @@ -2294,13 +2375,30 @@ function PRMergeCell({ const enabled = mergePresentation.autoMergeAction.kind === 'enable' setMerging(true) try { - const result = await window.api.gh.setPRAutoMerge({ - repoPath: repo.path, - repoId: repo.id, - prNumber: item.number, - enabled, - prRepo: item.prRepo ?? null - }) + const target = getActiveRuntimeTarget(sourceSettings) + const runtimeRepoId = + sourceContext?.provider === 'github' ? (sourceContext.repoId ?? repo.id) : repo.id + const result = + target.kind === 'environment' + ? await callRuntimeRpc<{ ok: boolean; error?: string }>( + target, + 'github.setPRAutoMerge', + { + repo: runtimeRepoId, + prNumber: item.number, + enabled, + prRepo: item.prRepo ?? null + }, + { timeoutMs: 30_000 } + ) + : await window.api.gh.setPRAutoMerge({ + repoPath: repo.path, + repoId: repo.id, + sourceContext, + prNumber: item.number, + enabled, + prRepo: item.prRepo ?? null + }) if (result.ok) { useAppStore.getState().recordFeatureInteraction('github-tasks') toast.success( @@ -3303,20 +3401,31 @@ export default function TaskPage(): React.JSX.Element { () => selectedRepos.find((r) => r.id === newIssueRepoId) ?? selectedRepos[0] ?? null, [selectedRepos, newIssueRepoId] ) + const newIssueSourceContext = useMemo( + () => getTaskPageRepoSourceContext(newIssueTargetRepo, 'github'), + [newIssueTargetRepo] + ) const newIssueRuntimeTarget = useMemo(() => { if (!newIssueTargetRepo?.id) { return null } - const targetSettings = getSettingsForRepoRuntimeOwner( + const repoOwnerSettings = getSettingsForRepoRuntimeOwner( { repos: [newIssueTargetRepo], settings }, newIssueTargetRepo.id ) + const targetSettings = + newIssueSourceContext?.provider === 'github' + ? { + ...repoOwnerSettings, + ...getTaskSourceRuntimeSettings(newIssueSourceContext) + } + : repoOwnerSettings const target = getActiveRuntimeTarget(targetSettings) if (target.kind !== 'environment') { return null } return repos.some((repo) => repo.id === newIssueTargetRepo.id) ? target : null - }, [newIssueTargetRepo, repos, settings]) + }, [newIssueSourceContext, newIssueTargetRepo, repos, settings]) const newIssueRepoLabels = useRepoLabels( newIssueOpen ? (newIssueTargetRepo?.path ?? null) : null, newIssueOpen ? (newIssueTargetRepo?.id ?? null) : null, @@ -5756,7 +5865,10 @@ export default function TaskPage(): React.JSX.Element { newIssueRuntimeTarget, 'github.createIssue', { - repo: newIssueTargetRepo.id, + repo: + newIssueSourceContext?.provider === 'github' + ? (newIssueSourceContext.repoId ?? newIssueTargetRepo.id) + : newIssueTargetRepo.id, title, body: newIssueBody, labels: newIssueLabels, @@ -5767,6 +5879,7 @@ export default function TaskPage(): React.JSX.Element { : await window.api.gh.createIssue({ repoPath: newIssueTargetRepo.path, repoId: newIssueTargetRepo.id, + sourceContext: newIssueSourceContext, title, body: newIssueBody, labels: newIssueLabels, @@ -5822,12 +5935,20 @@ export default function TaskPage(): React.JSX.Element { ? callRuntimeRpc>>( newIssueRuntimeTarget, 'github.workItem', - { repo: newIssueTargetRepo.id, number: result.number, type: 'issue' }, + { + repo: + newIssueSourceContext?.provider === 'github' + ? (newIssueSourceContext.repoId ?? newIssueTargetRepo.id) + : newIssueTargetRepo.id, + number: result.number, + type: 'issue' + }, { timeoutMs: 30_000 } ) : window.api.gh.workItem({ repoPath: newIssueTargetRepo.path, repoId: newIssueTargetRepo.id, + sourceContext: newIssueSourceContext, number: result.number, type: 'issue' }) @@ -5851,6 +5972,7 @@ export default function TaskPage(): React.JSX.Element { newIssueAssignees, newIssueLabels, newIssueRuntimeTarget, + newIssueSourceContext, newIssueSubmitting, newIssueTargetRepo, newIssueTitle, @@ -8344,14 +8466,22 @@ export default function TaskPage(): React.JSX.Element { {!showPRManagementColumns ? (
- +
) : null} {showPRManagementColumns ? ( <>
- +
@@ -8366,13 +8496,18 @@ export default function TaskPage(): React.JSX.Element { setTaskRefreshNonce((current) => current + 1)} />
) : (
- +
)} diff --git a/src/renderer/src/components/feature-interaction-writer-boundaries.test.ts b/src/renderer/src/components/feature-interaction-writer-boundaries.test.ts index d8767abbcf4..6e1757c7fa9 100644 --- a/src/renderer/src/components/feature-interaction-writer-boundaries.test.ts +++ b/src/renderer/src/components/feature-interaction-writer-boundaries.test.ts @@ -77,6 +77,26 @@ describe('feature interaction writer boundaries', () => { } }) + it('threads GitHub task source context through inline task mutations', () => { + const source = componentSource('TaskPage.tsx') + const sections = [ + sourceBetween(source, 'function GHStatusCell', 'function GitHubAssigneeAvatar'), + sourceBetween(source, 'function GHAssigneesCell', 'const triggerContent ='), + sourceBetween(source, 'function PRReviewCell', 'function PRChecksCell'), + componentBodyBeforeRender(source, 'PRMergeCell'), + sourceBetween(source, 'const handleCreateNewIssue', 'const handleCreateNewLinearProject') + ] + + for (const section of sections) { + expect(section).toContain('sourceContext') + } + const rowRenderStart = source.indexOf('filteredWorkItems.map((item) => {') + expect(rowRenderStart).toBeGreaterThanOrEqual(0) + expect(source.slice(rowRenderStart, rowRenderStart + 12_000)).toContain( + 'sourceContext={getTaskPageRepoSourceContext(itemRepo,' + ) + }) + it('suppresses Tasks surface telemetry for in-page provider switches and detail opens', () => { const source = componentSource('TaskPage.tsx') const suppression = 'recordTasksInteraction: false' @@ -91,9 +111,9 @@ describe('feature interaction writer boundaries', () => { sourceBetween(source, 'taskSourceManuallyChangedRef.current = true', 'void updateSettings') ] - expect(githubDetailSection).toContain("recordFeatureInteraction('github-tasks')") - expect(githubDetailSection).toContain('setDialogWorkItem(item, initialTab)') - expect(githubDetailSection).not.toContain('openTaskPage') + expect(githubDetailSection).toContain('openGitHubSourceContext') + expect(githubDetailSection).toContain('openTaskPage') + expect(githubDetailSection).toContain(suppression) for (const section of inPageNavigationSections) { expect(section).toContain(suppression)