History

Move PR checkout flow to dialog and drop /checkout-pr command

- Replace the inline composer PR menu with a dedicated `PullRequestThreadDialog` - Remove `/checkout-pr` from composer slash-command parsing and tests - Fix branch selector virtualization sizing for the checkout-PR row

Julius Marminge committed 8 months ago

Commit d28ced81 parent 07660805 files · +154 −166Browse files

Unified · Split

5 files
  1. apps/web/src/components/BranchToolbarBranchSelector.tsx +3 −2
  2. apps/web/src/components/ChatView.tsx +46 −79
  3. apps/web/src/components/PullRequestThreadDialog.tsx +99 −77
  4. apps/web/src/composer-logic.test.ts +4 −4
  5. apps/web/src/composer-logic.ts +2 −4
apps/web/src/components/BranchToolbarBranchSelector.tsx+3−2
@@ -291,7 +291,8 @@ export function BranchToolbarBranchSelector({ · 290 unmodified lines
291291 const branchListScrollElementRef = useRef<HTMLDivElement | null>(null);
292292 const branchListVirtualizer = useVirtualizer({
293293 count: filteredBranchPickerItems.length,
294− estimateSize: () => 28,
294+ estimateSize: (index) =>
295+ filteredBranchPickerItems[index] === checkoutPullRequestItemValue ? 44 : 28,
295296 getScrollElement: () => branchListScrollElementRef.current,
296297 overscan: 12,
297298 enabled: isBranchMenuOpen && shouldVirtualizeBranchList,
@@ -409,7 +410,7 @@ export function BranchToolbarBranchSelector({ · 111 unmodified lines
409410 items={branchPickerItems}
410411 filteredItems={filteredBranchPickerItems}
411412 autoHighlight
412− virtualized
413+ virtualized={shouldVirtualizeBranchList}
413414 onItemHighlighted={(_value, eventDetails) => {
414415 if (!isBranchMenuOpen || eventDetails.index < 0) return;
415416 branchListVirtualizer.scrollToIndex(eventDetails.index, { align: "auto" });
apps/web/src/components/ChatView.tsx+46−79
@@ -119,7 +119,6 @@ import { · 118 unmodified lines
119119 type TurnDiffTreeNode,
120120 } from "../lib/turnDiffTree";
121121 import BranchToolbar from "./BranchToolbar";
122−import { ComposerPullRequestCommandMenu } from "./ComposerPullRequestCommandMenu";
123122 import GitActionsControl from "./GitActionsControl";
124123 import {
125124 isOpenFavoriteEditorShortcut,
@@ -217,6 +216,7 @@ import { shouldUseCompactComposerFooter } from "./composerFooterLayout"; · 91 unmodified lines
217216 import { selectThreadTerminalState, useTerminalStateStore } from "../terminalStateStore";
218217 import { clamp } from "effect/Number";
219218 import { ComposerPromptEditor, type ComposerPromptEditorHandle } from "./ComposerPromptEditor";
219+import { PullRequestThreadDialog } from "./PullRequestThreadDialog";
220220 import { estimateTimelineMessageHeight } from "./timelineHeight";
221221
222222 function formatMessageMeta(createdAt: string, duration: string | null): string {
@@ -408,7 +408,7 @@ type ComposerCommandItem = · 185 unmodified lines
408408
409409 type SendPhase = "idle" | "preparing-worktree" | "sending-turn";
410410
411−interface PullRequestComposerMenuState {
411+interface PullRequestDialogState {
412412 initialReference: string | null;
413413 key: number;
414414 }
@@ -657,8 +657,9 @@ export default function ChatView({ threadId }: ChatViewProps) { · 242 unmodified lines
657657 const [nowTick, setNowTick] = useState(() => Date.now());
658658 const [terminalFocusRequestId, setTerminalFocusRequestId] = useState(0);
659659 const [composerHighlightedItemId, setComposerHighlightedItemId] = useState<string | null>(null);
660− const [pullRequestComposerMenuState, setPullRequestComposerMenuState] =
661− useState<PullRequestComposerMenuState | null>(null);
660+ const [pullRequestDialogState, setPullRequestDialogState] = useState<PullRequestDialogState | null>(
661+ null,
662+ );
662663 const [attachmentPreviewHandoffByMessageId, setAttachmentPreviewHandoffByMessageId] = useState<
663664 Record<string, string[]>
664665 >({});
@@ -764,19 +765,19 @@ export default function ChatView({ threadId }: ChatViewProps) { · 99 unmodified lines
764765 const latestTurnSettled = isLatestTurnSettled(activeLatestTurn, activeThread?.session ?? null);
765766 const activeProject = projects.find((p) => p.id === activeThread?.projectId);
766767
767− const openPullRequestComposerMenu = useCallback((reference?: string) => {
768+ const openPullRequestDialog = useCallback((reference?: string) => {
768769 if (!canCheckoutPullRequestIntoThread) {
769770 return;
770771 }
771− setPullRequestComposerMenuState({
772+ setPullRequestDialogState({
772773 initialReference: reference ?? null,
773774 key: Date.now(),
774775 });
775776 setComposerHighlightedItemId(null);
776777 }, [canCheckoutPullRequestIntoThread]);
777778
778− const closePullRequestComposerMenu = useCallback(() => {
779− setPullRequestComposerMenuState(null);
779+ const closePullRequestDialog = useCallback(() => {
780+ setPullRequestDialogState(null);
780781 }, []);
781782
782783 const openOrReuseProjectDraftThread = useCallback(
@@ -1254,7 +1255,6 @@ export default function ChatView({ threadId }: ChatViewProps) { · 471 unmodified lines
12541255 timelineEntries,
12551256 ]);
12561257 const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null;
1257− const pullRequestComposerMenuOpen = pullRequestComposerMenuState !== null;
12581258 const composerTriggerKind = composerTrigger?.kind ?? null;
12591259 const pathTriggerQuery = composerTrigger?.kind === "path" ? composerTrigger.query : "";
12601260 const isPathTrigger = composerTriggerKind === "path";
@@ -1311,22 +1311,12 @@ export default function ChatView({ threadId }: ChatViewProps) { · 50 unmodified lines
13111311 label: "/default",
13121312 description: "Switch this thread back to normal chat mode",
13131313 },
1314− {
1315− id: "slash:checkout-pr",
1316− type: "slash-command",
1317− command: "checkout-pr",
1318− label: "/checkout-pr",
1319− description: "Create a new thread from a GitHub pull request",
1320− },
13211314 ] satisfies ReadonlyArray<Extract<ComposerCommandItem, { type: "slash-command" }>>;
1322− const availableSlashCommandItems = canCheckoutPullRequestIntoThread
1323− ? slashCommandItems
1324− : slashCommandItems.filter((item) => item.command !== "checkout-pr");
13251315 const query = composerTrigger.query.trim().toLowerCase();
13261316 if (!query) {
1327− return [...availableSlashCommandItems];
1317+ return [...slashCommandItems];
13281318 }
1329− return availableSlashCommandItems.filter(
1319+ return slashCommandItems.filter(
13301320 (item) => item.command.includes(query) || item.label.slice(1).includes(query),
13311321 );
13321322 }
@@ -1347,16 +1337,14 @@ export default function ChatView({ threadId }: ChatViewProps) { · 14 unmodified lines
13471337 label: name,
13481338 description: `${providerLabel} · ${slug}`,
13491339 }));
1350− }, [canCheckoutPullRequestIntoThread, composerTrigger, searchableModelOptions, workspaceEntries]);
1351− const composerMenuOpen = Boolean(composerTrigger) || pullRequestComposerMenuOpen;
1340+ }, [composerTrigger, searchableModelOptions, workspaceEntries]);
1341+ const composerMenuOpen = Boolean(composerTrigger);
13521342 const activeComposerMenuItem = useMemo(
13531343 () =>
1354− pullRequestComposerMenuOpen
1355− ? null
1356− : (composerMenuItems.find((item) => item.id === composerHighlightedItemId) ??
1357− composerMenuItems[0] ??
1358− null),
1359− [composerHighlightedItemId, composerMenuItems, pullRequestComposerMenuOpen],
1344+ composerMenuItems.find((item) => item.id === composerHighlightedItemId) ??
1345+ composerMenuItems[0] ??
1346+ null,
1347+ [composerHighlightedItemId, composerMenuItems],
13601348 );
13611349 composerMenuOpenRef.current = composerMenuOpen;
13621350 composerMenuItemsRef.current = composerMenuItems;
@@ -2055,7 +2043,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 692 unmodified lines
20552043
20562044 useEffect(() => {
20572045 setExpandedWorkGroups({});
2058− setPullRequestComposerMenuState(null);
2046+ setPullRequestDialogState(null);
20592047 if (planSidebarOpenOnNextThreadRef.current) {
20602048 planSidebarOpenOnNextThreadRef.current = false;
20612049 setPlanSidebarOpen(true);
@@ -2070,16 +2058,12 @@ export default function ChatView({ threadId }: ChatViewProps) { · 8 unmodified lines
20702058 setComposerHighlightedItemId(null);
20712059 return;
20722060 }
2073− if (pullRequestComposerMenuOpen) {
2074− setComposerHighlightedItemId(null);
2075− return;
2076− }
20772061 setComposerHighlightedItemId((existing) =>
20782062 existing && composerMenuItems.some((item) => item.id === existing)
20792063 ? existing
20802064 : (composerMenuItems[0]?.id ?? null),
20812065 );
2082− }, [composerMenuItems, composerMenuOpen, pullRequestComposerMenuOpen]);
2066+ }, [composerMenuItems, composerMenuOpen]);
20832067
20842068 useEffect(() => {
20852069 setIsRevertingCheckpoint(false);
@@ -2589,15 +2573,8 @@ export default function ChatView({ threadId }: ChatViewProps) { · 503 unmodified lines
25892573 }
25902574 const standaloneSlashCommand =
25912575 composerImages.length === 0 ? parseStandaloneComposerSlashCommand(trimmed) : null;
2592− const shouldHandleStandaloneSlashCommand =
2593− standaloneSlashCommand !== null &&
2594− (standaloneSlashCommand !== "checkout-pr" || canCheckoutPullRequestIntoThread);
2595− if (shouldHandleStandaloneSlashCommand && standaloneSlashCommand) {
2596− if (standaloneSlashCommand === "checkout-pr") {
2597− openPullRequestComposerMenu();
2598− } else {
2599− await handleInteractionModeChange(standaloneSlashCommand);
2600− }
2576+ if (standaloneSlashCommand) {
2577+ await handleInteractionModeChange(standaloneSlashCommand);
26012578 promptRef.current = "";
26022579 clearComposerDraftContent(activeThread.id);
26032580 setComposerHighlightedItemId(null);
@@ -3386,11 +3363,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 782 unmodified lines
33863363 }
33873364 return;
33883365 }
3389− if (item.command === "checkout-pr") {
3390− openPullRequestComposerMenu();
3391− } else {
3392− void handleInteractionModeChange(item.command === "plan" ? "plan" : "default");
3393− }
3366+ void handleInteractionModeChange(item.command === "plan" ? "plan" : "default");
33943367 const applied = applyPromptReplacement(trigger.rangeStart, trigger.rangeEnd, "", {
33953368 expectedText: expectedToken,
33963369 });
@@ -3410,7 +3383,6 @@ export default function ChatView({ threadId }: ChatViewProps) { · 13 unmodified lines
34103383 [
34113384 applyPromptReplacement,
34123385 handleInteractionModeChange,
3413− openPullRequestComposerMenu,
34143386 onProviderModelSelect,
34153387 resolveActiveComposerTrigger,
34163388 ],
@@ -3485,12 +3457,6 @@ export default function ChatView({ threadId }: ChatViewProps) { · 68 unmodified lines
34853457 const { trigger } = resolveActiveComposerTrigger();
34863458 const menuIsActive = composerMenuOpenRef.current || trigger !== null;
34873459
3488− if (pullRequestComposerMenuOpen) {
3489− if (key === "ArrowDown" || key === "ArrowUp" || key === "Tab" || key === "Enter") {
3490− return true;
3491− }
3492− }
3493−
34943460 if (menuIsActive) {
34953461 const currentItems = composerMenuItemsRef.current;
34963462 if (key === "ArrowDown" && currentItems.length > 0) {
@@ -3712,28 +3678,15 @@ export default function ChatView({ threadId }: ChatViewProps) { · 215 unmodified lines
37123678 >
37133679 {composerMenuOpen && !isComposerApprovalState && (
37143680 <div className="absolute inset-x-0 bottom-full z-20 mb-2 px-1">
3715− {pullRequestComposerMenuState ? (
3716− <ComposerPullRequestCommandMenu
3717− key={pullRequestComposerMenuState.key}
3718− cwd={activeProject?.cwd ?? null}
3719− initialReference={pullRequestComposerMenuState.initialReference}
3720− onCancel={closePullRequestComposerMenu}
3721− onPrepared={async (input) => {
3722− await handlePreparedPullRequestThread(input);
3723− closePullRequestComposerMenu();
3724− }}
3725− />
3726− ) : (
3727− <ComposerCommandMenu
3728− items={composerMenuItems}
3729− resolvedTheme={resolvedTheme}
3730− isLoading={isComposerMenuLoading}
3731− triggerKind={composerTriggerKind}
3732− activeItemId={activeComposerMenuItem?.id ?? null}
3733− onHighlightedItemChange={onComposerMenuItemHighlighted}
3734− onSelect={onSelectComposerItem}
3735− />
3736− )}
3681+ <ComposerCommandMenu
3682+ items={composerMenuItems}
3683+ resolvedTheme={resolvedTheme}
3684+ isLoading={isComposerMenuLoading}
3685+ triggerKind={composerTriggerKind}
3686+ activeItemId={activeComposerMenuItem?.id ?? null}
3687+ onHighlightedItemChange={onComposerMenuItemHighlighted}
3688+ onSelect={onSelectComposerItem}
3689+ />
37373690 </div>
37383691 )}
37393692
@@ -4160,10 +4113,24 @@ export default function ChatView({ threadId }: ChatViewProps) { · 420 unmodified lines
41604113 envLocked={envLocked}
41614114 onComposerFocusRequest={scheduleComposerFocus}
41624115 {...(canCheckoutPullRequestIntoThread
4163− ? { onCheckoutPullRequestRequest: openPullRequestComposerMenu }
4116+ ? { onCheckoutPullRequestRequest: openPullRequestDialog }
41644117 : {})}
41654118 />
41664119 )}
4120+ {pullRequestDialogState ? (
4121+ <PullRequestThreadDialog
4122+ key={pullRequestDialogState.key}
4123+ open
4124+ cwd={activeProject?.cwd ?? null}
4125+ initialReference={pullRequestDialogState.initialReference}
4126+ onOpenChange={(open) => {
4127+ if (!open) {
4128+ closePullRequestDialog();
4129+ }
4130+ }}
4131+ onPrepared={handlePreparedPullRequestThread}
4132+ />
4133+ ) : null}
41674134 </div>{/* end chat column */}
41684135
41694136 {/* Plan sidebar */}
apps/web/src/components/PullRequestThreadDialog.tsxrenamed from apps/web/src/components/ComposerPullRequestCommandMenu.tsx+99−77
@@ -10,22 +10,33 @@ import { · 9 unmodified lines
1010 import { cn } from "~/lib/utils";
1111 import { parsePullRequestReference } from "~/pullRequestReference";
1212 import { Button } from "./ui/button";
13+import {
14+ Dialog,
15+ DialogDescription,
16+ DialogFooter,
17+ DialogHeader,
18+ DialogPanel,
19+ DialogPopup,
20+ DialogTitle,
21+} from "./ui/dialog";
1322 import { Input } from "./ui/input";
1423 import { Spinner } from "./ui/spinner";
1524
16−interface ComposerPullRequestCommandMenuProps {
25+interface PullRequestThreadDialogProps {
26+ open: boolean;
1727 cwd: string | null;
1828 initialReference: string | null;
19− onCancel: () => void;
29+ onOpenChange: (open: boolean) => void;
2030 onPrepared: (input: { branch: string; worktreePath: string | null }) => Promise<void> | void;
2131 }
2232
23−export function ComposerPullRequestCommandMenu({
33+export function PullRequestThreadDialog({
34+ open,
2435 cwd,
2536 initialReference,
26− onCancel,
37+ onOpenChange,
2738 onPrepared,
28−}: ComposerPullRequestCommandMenuProps) {
39+}: PullRequestThreadDialogProps) {
2940 const queryClient = useQueryClient();
3041 const referenceInputRef = useRef<HTMLInputElement>(null);
3142 const [reference, setReference] = useState(initialReference ?? "");
@@ -38,12 +49,14 @@ export function ComposerPullRequestCommandMenu({ · 6 unmodified lines
3849 );
3950
4051 useEffect(() => {
52+ if (!open) return;
4153 setReference(initialReference ?? "");
4254 setReferenceDirty(false);
4355 setPreparingMode(null);
44− }, [initialReference]);
56+ }, [initialReference, open]);
4557
4658 useEffect(() => {
59+ if (!open) return;
4760 const frame = window.requestAnimationFrame(() => {
4861 referenceInputRef.current?.focus();
4962 referenceInputRef.current?.select();
@@ -51,14 +64,14 @@ export function ComposerPullRequestCommandMenu({ · 1 unmodified line
5164 return () => {
5265 window.cancelAnimationFrame(frame);
5366 };
54− }, []);
67+ }, [open]);
5568
5669 const parsedReference = parsePullRequestReference(reference);
5770 const parsedDebouncedReference = parsePullRequestReference(debouncedReference);
5871 const resolvePullRequestQuery = useQuery(
5972 gitResolvePullRequestQueryOptions({
6073 cwd,
61− reference: parsedDebouncedReference,
74+ reference: open ? parsedDebouncedReference : null,
6275 }),
6376 );
6477 const cachedPullRequest = useMemo(() => {
@@ -83,6 +96,7 @@ export function ComposerPullRequestCommandMenu({ · 18 unmodified lines
8396 : null;
8497 const resolvedPullRequest = liveResolvedPullRequest ?? cachedPullRequest;
8598 const isResolving =
99+ open &&
86100 parsedReference !== null &&
87101 resolvedPullRequest === null &&
88102 (referenceDebouncer.state.isPending ||
@@ -120,11 +134,13 @@ export function ComposerPullRequestCommandMenu({ · 31 unmodified lines
120134 branch: result.branch,
121135 worktreePath: result.worktreePath,
122136 });
137+ onOpenChange(false);
123138 } finally {
124139 setPreparingMode(null);
125140 }
126141 }, [
127142 cwd,
143+ onOpenChange,
128144 onPrepared,
129145 parsedReference,
130146 preparePullRequestThreadMutation,
@@ -135,10 +151,10 @@ export function ComposerPullRequestCommandMenu({ · 4 unmodified lines
135151 !referenceDirty
136152 ? null
137153 : reference.trim().length === 0
138− ? "Paste a GitHub pull request URL or enter 123 / #123."
139− : parsedReference === null
140− ? "Use a GitHub pull request URL, 123, or #123."
141− : null;
154+ ? "Paste a GitHub pull request URL or enter 123 / #123."
155+ : parsedReference === null
156+ ? "Use a GitHub pull request URL, 123, or #123."
157+ : null;
142158 const errorMessage =
143159 validationMessage ??
144160 (resolvedPullRequest === null && resolvePullRequestQuery.isError
@@ -152,73 +168,79 @@ export function ComposerPullRequestCommandMenu({ · 7 unmodified lines
152168 : null));
153169
154170 return (
155− <div className="overflow-hidden rounded-xl border border-border/80 bg-popover/96 shadow-lg/8 backdrop-blur-xs">
156− <div className="space-y-4 p-3">
157− <div className="space-y-2">
158− <div className="flex items-center justify-between gap-3">
159− <div>
160− <p className="font-medium text-sm">Checkout Pull Request</p>
161− <p className="text-muted-foreground text-xs">
162− Resolve a PR, then choose Local or Worktree for the new draft thread.
163− </p>
164− </div>
165− </div>
166− <Input
167− autoFocus
168− ref={referenceInputRef}
169− placeholder="https://github.com/owner/repo/pull/42 or #42"
170− value={reference}
171− onChange={(event) => {
172− setReferenceDirty(true);
173− setReference(event.target.value);
174− }}
175− onKeyDown={(event) => {
176− if (event.key !== "Enter") {
177− return;
178− }
179− event.preventDefault();
180− if (!isResolving && !preparePullRequestThreadMutation.isPending) {
181− void handleConfirm("local");
182− }
183− }}
184− />
185− </div>
171+ <Dialog
172+ open={open}
173+ onOpenChange={(nextOpen) => {
174+ if (!preparePullRequestThreadMutation.isPending) {
175+ onOpenChange(nextOpen);
176+ }
177+ }}
178+ >
179+ <DialogPopup className="max-w-xl">
180+ <DialogHeader>
181+ <DialogTitle>Checkout Pull Request</DialogTitle>
182+ <DialogDescription>
183+ Resolve a GitHub pull request, then create the draft thread in the main repo or in a
184+ dedicated worktree.
185+ </DialogDescription>
186+ </DialogHeader>
187+ <DialogPanel className="space-y-4">
188+ <label className="grid gap-1.5">
189+ <span className="text-xs font-medium text-foreground">Pull request</span>
190+ <Input
191+ ref={referenceInputRef}
192+ placeholder="https://github.com/owner/repo/pull/42 or #42"
193+ value={reference}
194+ onChange={(event) => {
195+ setReferenceDirty(true);
196+ setReference(event.target.value);
197+ }}
198+ onKeyDown={(event) => {
199+ if (event.key !== "Enter") {
200+ return;
201+ }
202+ event.preventDefault();
203+ if (!isResolving && !preparePullRequestThreadMutation.isPending) {
204+ void handleConfirm("local");
205+ }
206+ }}
207+ />
208+ </label>
186209
187− {resolvedPullRequest ? (
188− <div className="rounded-xl border border-border/70 bg-muted/24 p-3">
189− <div className="flex items-start justify-between gap-3">
190− <div className="min-w-0">
191− <p className="truncate font-medium text-sm">{resolvedPullRequest.title}</p>
192− <p className="truncate text-muted-foreground text-xs">
193− #{resolvedPullRequest.number} · {resolvedPullRequest.headBranch} to{" "}
194− {resolvedPullRequest.baseBranch}
195− </p>
210+ {resolvedPullRequest ? (
211+ <div className="rounded-xl border border-border/70 bg-muted/24 p-3">
212+ <div className="flex items-start justify-between gap-3">
213+ <div className="min-w-0">
214+ <p className="truncate font-medium text-sm">{resolvedPullRequest.title}</p>
215+ <p className="truncate text-muted-foreground text-xs">
216+ #{resolvedPullRequest.number} · {resolvedPullRequest.headBranch} to{" "}
217+ {resolvedPullRequest.baseBranch}
218+ </p>
219+ </div>
220+ <span className={cn("shrink-0 text-xs capitalize", statusTone)}>
221+ {resolvedPullRequest.state}
222+ </span>
196223 </div>
197− <span className={cn("shrink-0 text-xs capitalize", statusTone)}>
198− {resolvedPullRequest.state}
199− </span>
200224 </div>
201− </div>
202− ) : null}
203−
204− {isResolving ? (
205− <div className="flex items-center gap-2 text-muted-foreground text-xs">
206− <Spinner className="size-3.5" />
207− Resolving pull request...
208− </div>
209− ) : null}
225+ ) : null}
210226
211− {errorMessage ? <p className="text-destructive text-xs">{errorMessage}</p> : null}
212− </div>
227+ {isResolving ? (
228+ <div className="flex items-center gap-2 text-muted-foreground text-xs">
229+ <Spinner className="size-3.5" />
230+ Resolving pull request...
231+ </div>
232+ ) : null}
213233
214− <div className="flex items-center justify-between gap-2 border-t px-3 py-2">
215− <p className="text-muted-foreground text-xs">
216− {resolvedPullRequest
217− ? "Create the draft thread in the main repo or in a dedicated worktree."
218− : "Resolve the pull request first."}
219− </p>
220− <div className="flex items-center gap-2">
221− <Button type="button" variant="ghost" size="sm" onClick={onCancel}>
234+ {errorMessage ? <p className="text-destructive text-xs">{errorMessage}</p> : null}
235+ </DialogPanel>
236+ <DialogFooter>
237+ <Button
238+ type="button"
239+ variant="outline"
240+ size="sm"
241+ onClick={() => onOpenChange(false)}
242+ disabled={preparePullRequestThreadMutation.isPending}
243+ >
222244 Cancel
223245 </Button>
224246 <Button
@@ -252,8 +274,8 @@ export function ComposerPullRequestCommandMenu({ · 27 unmodified lines
252274 >
253275 {preparingMode === "worktree" ? "Preparing worktree..." : "Worktree"}
254276 </Button>
255− </div>
256− </div>
257− </div>
277+ </DialogFooter>
278+ </DialogPopup>
279+ </Dialog>
258280 );
259281 }
apps/web/src/composer-logic.test.ts+4−4
@@ -134,11 +134,11 @@ describe("parseStandaloneComposerSlashCommand", () => { · 133 unmodified lines
134134 expect(parseStandaloneComposerSlashCommand("/default")).toBe("default");
135135 });
136136
137− it("parses standalone /checkout-pr command", () => {
138− expect(parseStandaloneComposerSlashCommand(" /checkout-pr ")).toBe("checkout-pr");
139− });
140−
141137 it("ignores slash commands with extra message text", () => {
142138 expect(parseStandaloneComposerSlashCommand("/plan explain this")).toBeNull();
143139 });
140+
141+ it("ignores removed /checkout-pr command", () => {
142+ expect(parseStandaloneComposerSlashCommand(" /checkout-pr ")).toBeNull();
143+ });
144144 });
apps/web/src/composer-logic.ts+2−4
@@ -1,7 +1,7 @@
11 import { splitPromptIntoComposerSegments } from "./composer-editor-mentions";
22
33 export type ComposerTriggerKind = "path" | "slash-command" | "slash-model";
4−export type ComposerSlashCommand = "model" | "plan" | "default" | "checkout-pr";
4+export type ComposerSlashCommand = "model" | "plan" | "default";
55
66 export interface ComposerTrigger {
77 kind: ComposerTriggerKind;
@@ -14,7 +14,6 @@ const SLASH_COMMANDS: readonly ComposerSlashCommand[] = [ · 6 unmodified lines
1414 "model",
1515 "plan",
1616 "default",
17− "checkout-pr",
1817 ];
1918
2019 function clampCursor(text: string, cursor: number): number {
@@ -169,13 +168,12 @@ export function parseStandaloneComposerSlashCommand(text: string): Exclude< · 148 unmodified lines
169168 ComposerSlashCommand,
170169 "model"
171170 > | null {
172− const match = /^\/(plan|default|checkout-pr)\s*$/i.exec(text.trim());
171+ const match = /^\/(plan|default)\s*$/i.exec(text.trim());
173172 if (!match) {
174173 return null;
175174 }
176175 const command = match[1]?.toLowerCase();
177176 if (command === "plan") return "plan";
178− if (command === "checkout-pr") return "checkout-pr";
179177 return "default";
180178 }
181179