fix(app): auto-set ref picker from a single attached PR (#951)

* fix(app): auto-set ref picker from a single attached PR

Attaching a PR in the new-workspace composer signals "I want this PR's
context", and almost always also means "base the worktree on this PR".
Until now the two paths were independent: the ref picker stayed on the
default branch, so the worktree was branched off main even though the
sidebar chip looked like a PR checkout.

Auto-promote the picker when exactly one PR is attached and the user
hasn't manually picked a ref. Manual picker selections are preserved;
auto-promoted ones are cleared when their backing attachment is removed.

* refactor(app): derive auto-promoted picker selection instead of syncing

The previous version drove `pickerSelection` from a useEffect that
inspected attachments and overwrote state. That meant the shared
`PickerSelection` type had to grow a `source: "manual" | "attachment"`
discriminator just so the effect could tell its own writes apart from
manual picks. Classic effect-as-derived-state.

Split the state instead. `manualPickerSelection` holds explicit picks;
`autoPickerItem` is a useMemo over composer attachments; the effective
`pickerSelection` is `manual ?? auto` via a small precedence helper. No
effect, no `source` flag, and `PickerSelection` stays a screen-local
shape since nothing outside needs it.
This commit is contained in:
Mohamed Boudra
2026-05-12 21:08:47 +08:00
committed by GitHub
parent ad88682426
commit b9cea49f10
3 changed files with 76 additions and 5 deletions
@@ -33,3 +33,19 @@ export function syncPickerPrAttachment(input: {
return { attachments: nextAttachments, attachedPrNumber };
}
// Suggest a ref picker target derived from composer attachments alone. When the
// user attaches exactly one PR they almost always want the worktree based on
// that PR — surface it as the picker's default so the worktree isn't silently
// branched off main. The caller is responsible for letting manual picks win.
export function deriveAutoPickerItemFromAttachments(
attachments: ReadonlyArray<UserComposerAttachment>,
): PickerItem | null {
let onlyPr: Extract<UserComposerAttachment, { kind: "github_pr" }> | null = null;
for (const attachment of attachments) {
if (attachment.kind !== "github_pr") continue;
if (onlyPr) return null;
onlyPr = attachment;
}
return onlyPr ? { kind: "github-pr", item: onlyPr.item } : null;
}