From 2e6d66077fb1b4839c92ed11d0fb4e00a930381f Mon Sep 17 00:00:00 2001 From: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com> Date: Sun, 1 Nov 2026 23:55:20 +0000 Subject: [PATCH] fix(mobile): keep Resolve and its feedback after the PR tab's conflicts box went away (#37234) The diff-review dock has no next-step bar, so its bottom action now offers Resolve with for a conflicting pull request, with the launch notice. In the hub the bar shows its agent launch notice on the Pull request tab too, since the removed box was the one that showed it there, and an Unlink failure reaches the bar's notice now that the PR tab has no footer. --- .../pr-sidebar/PrTabBottomAction.tsx | 54 ++++++++++++++++++- .../MobileSourceControlPanel.tsx | 2 +- .../source-control-status-retry.test.tsx | 1 - .../use-mobile-source-control-next-step.ts | 9 ++-- 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx b/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx index b4be5638138..6d0224f4a4b 100644 --- a/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx +++ b/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx @@ -1,5 +1,5 @@ import { ActivityIndicator, Pressable, Text, View } from 'react-native' -import { GitMerge, GitPullRequest, type LucideIcon } from 'lucide-react-native' +import { GitMerge, GitPullRequest, Sparkles, type LucideIcon } from 'lucide-react-native' import { colors, feedback, pressedStyle } from '../../theme/mobile-theme' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' import { useMobileTranslation } from '../../i18n/use-mobile-translation' @@ -7,19 +7,33 @@ import { resolveGitHubPRMergeMethods } from '../../../../src/shared/github/pull- import { resolveGitHubPRMergeState } from '../../../../src/shared/github/pull-request-merge-state' import { MOBILE_GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY } from '../../../../src/shared/source-control-runtime-capabilities' import { useRouteCapability } from '../../transport/use-workspace-client' +import { buildResolveConflictsPrompt } from '../../session/pr-ai-triage-prompt' +import { useMobileLaunchAgentLabel } from '../../session/use-mobile-launch-agent-label' +import { useHostProtocolGates } from '../host-protocol-gates-context' +import { AgentLaunchNotice } from '../AgentLaunchNotice' import { presentPrMerge } from './pr-actions-state' import type { MobilePrTab } from './use-mobile-pr-tab' import { prTabStyles as styles } from './pr-tab-styles' type Tone = 'primary' | 'merge' | 'secondary' -// The PR tab's foot: the diff-review dock always shows it, the hub only while its bar is on a local step. +// The diff-review dock's bottom action; in the hub the next-step bar is the one bottom action instead. export function PrTabBottomAction({ tab, bottomInset }: { tab: MobilePrTab; bottomInset: number }) { useMobileColorScheme() const t = useMobileTranslation() const { state, actions } = tab const canMarkReady = useRouteCapability(MOBILE_GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY) const footerStyle = [styles.footer, { paddingBottom: Math.max(bottomInset, 12) }] + const conflicting = + state.kind === 'ready' && resolveGitHubPRMergeState(state.data.pr).kind === 'conflicts' + const { hostCapabilities } = useHostProtocolGates() + const agent = useMobileLaunchAgentLabel({ + client: tab.client, + worktreeId: tab.worktreeId, + hostCapabilities, + actionId: 'resolveConflicts', + enabled: conflicting + }) if (state.kind === 'none') { return ( @@ -82,6 +96,42 @@ export function PrTabBottomAction({ tab, bottomInset }: { tab: MobilePrTab; bott const merge = presentPrMerge(effective, defaultMethod) const decision = resolveGitHubPRMergeState(effective) const autoMergeBusy = actions.isBusy({ kind: 'autoMerge' }) + // The dock has no bar, so conflicts get the bar's own fix here, named by the button rather than a reason line. + if (decision.kind === 'conflicts') { + const notice = tab.triage.noticeFor('resolve-conflicts') + return ( + + {errorLine} + + + void tab.triage.launch('resolve-conflicts', () => + buildResolveConflictsPrompt({ + prNumber: pr.number, + baseRef: pr.conflictSummary?.baseRef ?? pr.baseRefName ?? null, + files: pr.conflictSummary?.files ?? [], + worktreeId: tab.worktreeId + }) + ) + } + busy={tab.triage.isBusy('resolve-conflicts')} + disabled={tab.triage.availability !== 'available'} + /> + + ) + } const reason = merge.hint ? ( diff --git a/mobile/src/source-control/MobileSourceControlPanel.tsx b/mobile/src/source-control/MobileSourceControlPanel.tsx index 0385302c637..fc162b47d91 100644 --- a/mobile/src/source-control/MobileSourceControlPanel.tsx +++ b/mobile/src/source-control/MobileSourceControlPanel.tsx @@ -371,7 +371,7 @@ export function MobileSourceControlPanel({ {ready ? ( ({ vi.mock('./MobileSourceControlNextStepBar', () => ({ MobileSourceControlNextStepBar: () => null })) vi.mock('./use-mobile-source-control-next-step', () => ({ useMobileSourceControlNextStep: () => ({ - offersMerge: false, view: { kind: 'hidden' }, notice: null, agentNotice: null, diff --git a/mobile/src/source-control/use-mobile-source-control-next-step.ts b/mobile/src/source-control/use-mobile-source-control-next-step.ts index 2d1f52fdaa5..e483b80bee6 100644 --- a/mobile/src/source-control/use-mobile-source-control-next-step.ts +++ b/mobile/src/source-control/use-mobile-source-control-next-step.ts @@ -253,11 +253,10 @@ export function useMobileSourceControlNextStep({ : step.kind === 'resolve_conflicts' ? 'resolve-conflicts' : null - // On the PR tab the body's own AI button already shows this launcher's notice. - const agentNotice = - triageKey && !showWorkflowErrors - ? { ...triage.noticeFor(triageKey), availability: triage.availability } - : null + // The bar's agent button is the only one on either tab, so its launch notice shows under it. + const agentNotice = triageKey + ? { ...triage.noticeFor(triageKey), availability: triage.availability } + : null const workflowError = showWorkflowErrors ? (state.actionError ?? state.commitFailureRecovery?.error ?? null) : null