* 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.
52 lines
2.1 KiB
TypeScript
52 lines
2.1 KiB
TypeScript
import type { UserComposerAttachment } from "@/attachments/types";
|
|
import type { PickerItem } from "./new-workspace-picker-item";
|
|
|
|
// The picker "owns" at most one PR attachment at a time. When the user selects
|
|
// a different item the previously-owned PR is removed before the new one is added.
|
|
// User-added attachments for other PRs/issues are left untouched.
|
|
export function syncPickerPrAttachment(input: {
|
|
attachments: UserComposerAttachment[];
|
|
previousPickerPrNumber: number | null;
|
|
item: PickerItem;
|
|
}): { attachments: UserComposerAttachment[]; attachedPrNumber: number | null } {
|
|
let nextAttachments = input.attachments;
|
|
let attachedPrNumber: number | null = null;
|
|
|
|
if (input.previousPickerPrNumber !== null) {
|
|
nextAttachments = nextAttachments.filter(
|
|
(attachment) =>
|
|
attachment.kind !== "github_pr" || attachment.item.number !== input.previousPickerPrNumber,
|
|
);
|
|
}
|
|
|
|
if (input.item.kind === "github-pr") {
|
|
const selectedPr = input.item.item;
|
|
const hasExistingPrAttachment = nextAttachments.some(
|
|
(attachment) =>
|
|
attachment.kind === "github_pr" && attachment.item.number === selectedPr.number,
|
|
);
|
|
if (!hasExistingPrAttachment) {
|
|
nextAttachments = [...nextAttachments, { kind: "github_pr", item: selectedPr }];
|
|
attachedPrNumber = selectedPr.number;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|