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:
@@ -33,7 +33,10 @@ import type { AgentAttachment, GitHubSearchItem } from "@server/shared/messages"
|
||||
import type { AgentProvider } from "@server/server/agent/agent-sdk-types";
|
||||
import { isEmptyWorkspaceSubmission, runCreateEmptyWorkspace } from "./new-workspace-empty";
|
||||
import { pickerItemToCheckoutRequest, type PickerItem } from "./new-workspace-picker-item";
|
||||
import { syncPickerPrAttachment } from "./new-workspace-picker-state";
|
||||
import {
|
||||
deriveAutoPickerItemFromAttachments,
|
||||
syncPickerPrAttachment,
|
||||
} from "./new-workspace-picker-state";
|
||||
|
||||
interface NewWorkspaceScreenProps {
|
||||
serverId: string;
|
||||
@@ -51,6 +54,17 @@ interface PickerSelection {
|
||||
attachedPrNumber: number | null;
|
||||
}
|
||||
|
||||
// Manual picks always win; the auto-promoted item is a fallback so the user
|
||||
// doesn't silently get "main" when they meant the PR they just attached.
|
||||
function combinePickerSelection(
|
||||
manual: PickerSelection | null,
|
||||
autoItem: PickerItem | null,
|
||||
): PickerSelection | null {
|
||||
if (manual) return manual;
|
||||
if (autoItem) return { item: autoItem, attachedPrNumber: null };
|
||||
return null;
|
||||
}
|
||||
|
||||
const BRANCH_OPTION_PREFIX = "branch:";
|
||||
const PR_OPTION_PREFIX = "github-pr:";
|
||||
|
||||
@@ -400,7 +414,7 @@ export function NewWorkspaceScreen({
|
||||
typeof normalizeWorkspaceDescriptor
|
||||
> | null>(null);
|
||||
const [pendingAction, setPendingAction] = useState<"chat" | "empty" | null>(null);
|
||||
const [pickerSelection, setPickerSelection] = useState<PickerSelection | null>(null);
|
||||
const [manualPickerSelection, setManualPickerSelection] = useState<PickerSelection | null>(null);
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
const [pickerSearchQuery, setPickerSearchQuery] = useState("");
|
||||
const [debouncedPickerSearchQuery, setDebouncedPickerSearchQuery] = useState("");
|
||||
@@ -414,7 +428,6 @@ export function NewWorkspaceScreen({
|
||||
|
||||
const displayName = displayNameProp?.trim() ?? "";
|
||||
const workspace = createdWorkspace;
|
||||
const selectedItem = pickerSelection?.item ?? null;
|
||||
const isPending = pendingAction !== null;
|
||||
const client = useHostRuntimeClient(serverId);
|
||||
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||
@@ -430,6 +443,13 @@ export function NewWorkspaceScreen({
|
||||
});
|
||||
const composerState = chatDraft.composerState;
|
||||
|
||||
const autoPickerItem = useMemo(
|
||||
() => deriveAutoPickerItemFromAttachments(chatDraft.attachments),
|
||||
[chatDraft.attachments],
|
||||
);
|
||||
const pickerSelection = combinePickerSelection(manualPickerSelection, autoPickerItem);
|
||||
const selectedItem = pickerSelection?.item ?? null;
|
||||
|
||||
const withConnectedClient = useCallback(() => {
|
||||
if (!client || !isConnected) {
|
||||
throw new Error("Host is not connected");
|
||||
@@ -525,7 +545,7 @@ export function NewWorkspaceScreen({
|
||||
item,
|
||||
});
|
||||
|
||||
setPickerSelection({
|
||||
setManualPickerSelection({
|
||||
item,
|
||||
attachedPrNumber: next.attachedPrNumber,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user