feat(mobile): surface "Link an existing PR" in PR sidebar empty state (#5963)

* feat(mobile): surface "Link an existing PR" in PR sidebar empty state

The mobile link-PR building blocks (MobileLinkPrForm, linkMobilePr,
parseGitHubPrReference) existed and were tested, but had no entry point —
only unlink was wired up. Add a "Link an existing PR" action to the no-PR
empty state that opens MobileLinkPrForm and refetches the sidebar on
success, mirroring desktop's link flow (GitHub-scoped via worktree.set).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Polish mobile PR link empty state

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
This commit is contained in:
gsxdsm 2026-06-22 15:16:42 -07:00 committed by GitHub
parent 20a51978e5
commit 375c4df8b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 63 additions and 6 deletions

View File

@ -1,12 +1,13 @@
import { useEffect, useState } from 'react'
import { ActivityIndicator, Pressable, Text, View } from 'react-native'
import { GitPullRequestArrow, RefreshCw } from 'lucide-react-native'
import { GitPullRequestArrow, Link2, RefreshCw } from 'lucide-react-native'
import { colors } from '../../theme/mobile-theme'
import type { RpcClient } from '../../transport/rpc-client'
import { resolveMobilePrPrefill, type MobilePrPrefill } from '../../source-control/mobile-pr-create'
import { fetchWorktreeLinkedPR } from '../../source-control/mobile-pr-link'
import { openMobilePrUrl } from '../MobilePrComposeSheet'
import { MobilePrComposeForm } from './MobilePrComposeForm'
import { MobileLinkPrForm } from './MobileLinkPrForm'
import { prCreateEmptyStateStyles as styles } from './pr-create-empty-state-styles'
type Props = {
@ -17,18 +18,18 @@ type Props = {
onCreated: () => void
}
type Mode = 'choose' | 'create'
type Mode = 'choose' | 'create' | 'link'
// Empty state for a branch with no PR. Keep this scoped to desktop's no-PR
// surface: create/refresh here; linked-PR edits belong outside this panel.
// Empty state for a branch with no PR: create a new PR, or link an existing one
// (the no-PR surface is the natural home for linking — desktop's link entry lives
// on its PR card, but on mobile this is where a user lands with nothing linked).
export function PrSidebarCreateEmptyState({ client, worktreeId, gitBranch, onCreated }: Props) {
const [prefill, setPrefill] = useState<MobilePrPrefill | null>(null)
const [mode, setMode] = useState<Mode>('choose')
const [loading, setLoading] = useState(false)
const [createWarning, setCreateWarning] = useState<string | null>(null)
// A persisted linkedPR while the branch shows no PR means the linked PR could
// not be resolved. Mention it, but keep link editing out of this desktop-parity
// create surface.
// not be resolved. Mention it while still allowing the user to relink.
const [orphanLinkedPR, setOrphanLinkedPR] = useState<number | null>(null)
useEffect(() => {
@ -103,6 +104,22 @@ export function PrSidebarCreateEmptyState({ client, worktreeId, gitBranch, onCre
)
}
if (mode === 'link') {
return (
<View style={styles.composerArea}>
<MobileLinkPrForm
client={client}
worktreeId={worktreeId}
onCancel={() => setMode('choose')}
onLinked={() => {
setMode('choose')
onCreated()
}}
/>
</View>
)
}
return (
<View style={styles.section}>
<View style={styles.header}>
@ -148,6 +165,26 @@ export function PrSidebarCreateEmptyState({ client, worktreeId, gitBranch, onCre
: 'The current branch is not linked to an open PR.'}
</Text>
{createWarning ? <Text style={styles.bodyText}>{createWarning}</Text> : null}
<Pressable
style={({ pressed }) => [
styles.linkButton,
!client && styles.linkButtonDisabled,
pressed && styles.linkButtonPressed
]}
onPress={() => setMode('link')}
disabled={!client}
accessibilityRole="button"
accessibilityLabel="Link an existing pull request"
accessibilityState={{ disabled: !client }}
hitSlop={6}
>
<Link2
size={14}
color={client ? colors.textSecondary : colors.textMuted}
strokeWidth={2.2}
/>
<Text style={styles.linkButtonText}>Link an existing PR</Text>
</Pressable>
</View>
</View>
)

View File

@ -83,5 +83,25 @@ export const prCreateEmptyStateStyles = StyleSheet.create({
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.borderSubtle,
padding: spacing.md
},
// Secondary link-an-existing-PR affordance, set apart from the body copy.
linkButton: {
marginTop: spacing.xs,
minHeight: 32,
alignSelf: 'flex-start',
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs
},
linkButtonDisabled: {
opacity: 0.5
},
linkButtonPressed: {
opacity: 0.6
},
linkButtonText: {
color: colors.textSecondary,
fontSize: typography.metaSize,
fontWeight: '600'
}
})