Route GitHub task row actions by source context

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-12 20:28:00 -07:00
co-authored by Orca
parent bad7711930
commit bf55667eaa
7 changed files with 214 additions and 49 deletions
@@ -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`.
+4
View File
@@ -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'
}
+10 -9
View File
@@ -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' }
+2
View File
@@ -1142,6 +1142,7 @@ export type PreloadApi = {
workItem: (args: {
repoPath: string
repoId?: string
sourceContext?: TaskSourceContext | null
number: number
type?: 'issue' | 'pr'
}) => Promise<Omit<GitHubWorkItem, 'repoId'> | null>
@@ -1177,6 +1178,7 @@ export type PreloadApi = {
createIssue: (args: {
repoPath: string
repoId?: string
sourceContext?: TaskSourceContext | null
title: string
body: string
labels?: string[]
+2
View File
@@ -950,6 +950,7 @@ const api = {
workItem: (args: {
repoPath: string
repoId?: string
sourceContext?: TaskSourceContext | null
number: number
type?: 'issue' | 'pr'
}): Promise<unknown> => 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[]
+172 -37
View File
@@ -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<string | null>(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<HTMLInputElement | null>(null)
const reviewerInputFocusFrameRef = useRef<number | null>(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 (
<span className="text-[11px] text-muted-foreground">
@@ -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<Awaited<ReturnType<typeof window.api.gh.workItem>>>(
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 ? (
<div className="min-w-0 flex items-center text-xs text-muted-foreground">
<GHAssigneesCell item={item} repo={itemRepo ?? null} />
<GHAssigneesCell
item={item}
repo={itemRepo ?? null}
sourceContext={getTaskPageRepoSourceContext(itemRepo, 'github')}
/>
</div>
) : null}
{showPRManagementColumns ? (
<>
<div className="flex min-w-0 items-center">
<PRReviewCell item={item} repo={itemRepo ?? null} />
<PRReviewCell
item={item}
repo={itemRepo ?? null}
sourceContext={getTaskPageRepoSourceContext(itemRepo, 'github')}
/>
</div>
<div className="flex min-w-0 items-center">
@@ -8366,13 +8496,18 @@ export default function TaskPage(): React.JSX.Element {
<PRMergeCell
item={item}
repo={itemRepo ?? null}
sourceContext={getTaskPageRepoSourceContext(itemRepo, 'github')}
onRefresh={() => setTaskRefreshNonce((current) => current + 1)}
/>
</div>
</>
) : (
<div className="flex items-center">
<GHStatusCell item={item} repo={itemRepo ?? null} />
<GHStatusCell
item={item}
repo={itemRepo ?? null}
sourceContext={getTaskPageRepoSourceContext(itemRepo, 'github')}
/>
</div>
)}
@@ -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)