feat: instant draft-first worktree creation and multi-run launcher redesign (#741)

## Summary

- **Instant worktree creation from chat draft**: selecting "+ New worktree" in the draft branch selector immediately creates a session draft and bootstraps the worktree in the background — no modal interruption
- **Redesigned multi-run launcher**: compact 2-column grid layout in a right-sized dialog with scroll shadow, sticky footer, tooltips replacing verbose descriptions, and project icons in the selector
- **Branch selector aligned across surfaces**: multi-run and agent manager branch pickers now use the shared git store and match NewWorktreeDialog behavior (same default resolution cascade, no synthetic HEAD option, all branches shown)
- **Opaque model multi-select dropdown**: fixes text bleed-through on translucent backgrounds by compositing `--surface-elevated` over `--surface-background`
- **"+ New" inline button in sidebar worktree headers** for faster worktree creation

## Why

Worktree creation was behind modal flow that interrupted the user's train of thought. The draft-first approach lets users start typing immediately while the worktree bootstraps. The multi-run launcher had an oversized form layout with redundant explanations, and its branch picker behaved differently from the main worktree dialog - causing confusion about which branches were available and what the default was.
This commit is contained in:
Bohdan Triapitsyn
2026-03-22 22:31:29 +02:00
committed by GitHub
parent c66d480782
commit 53c2a0d919
40 changed files with 1850 additions and 909 deletions
@@ -18,6 +18,7 @@ import {
RiExpandUpDownLine,
RiStickyNoteLine,
} from '@remixicon/react';
import { ArrowsMerge } from '@/components/icons/ArrowsMerge';
import type { ProjectRef } from '@/lib/openchamberConfig';
import { useSessionDisplayStore } from '@/stores/useSessionDisplayStore';
import { ProjectNotesTodoPanel } from '../ProjectNotesTodoPanel';
@@ -31,6 +32,8 @@ type Props = {
setProjectNotesPanelOpen: (open: boolean) => void;
activeProjectRefForHeader: ProjectRef | null;
activeProjectLabelForHeader: string | null;
canOpenMultiRun: boolean;
openMultiRunLauncher: () => void;
stableActiveProjectIsRepo: boolean;
headerActionIconClass: string;
reserveHeaderActionsSpace: boolean;
@@ -56,6 +59,8 @@ export function SidebarHeader(props: Props): React.ReactNode {
setProjectNotesPanelOpen,
activeProjectRefForHeader,
activeProjectLabelForHeader,
canOpenMultiRun,
openMultiRunLauncher,
stableActiveProjectIsRepo,
headerActionIconClass,
reserveHeaderActionsSpace,
@@ -113,6 +118,21 @@ export function SidebarHeader(props: Props): React.ReactNode {
</div>
<div className="flex items-center gap-1.5">
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={openMultiRunLauncher}
className={headerActionButtonClass}
aria-label="New multi-run"
disabled={!canOpenMultiRun}
>
<ArrowsMerge className={headerActionIconClass} />
</button>
</TooltipTrigger>
<TooltipContent side="bottom" sideOffset={4}><p>New multi-run</p></TooltipContent>
</Tooltip>
{useMobileNotesPanel ? (
<Tooltip>
<TooltipTrigger asChild>
@@ -54,7 +54,6 @@ type Props = {
setSessionSwitcherOpen: (open: boolean) => void;
openNewSessionDraft: (options?: { directoryOverride?: string | null }) => void;
openNewWorktreeDialog: () => void;
openMultiRunLauncher: () => void;
openProjectEditDialog: (id: string) => void;
removeProject: (id: string) => void;
projectHeaderSentinelRefs: React.MutableRefObject<Map<string, HTMLDivElement | null>>;
@@ -185,10 +184,6 @@ export function SidebarProjectsList(props: Props): React.ReactNode {
if (props.mobileVariant) props.setSessionSwitcherOpen(false);
props.openNewWorktreeDialog();
}}
onOpenMultiRunLauncher={() => {
if (projectKey !== props.activeProjectId) props.setActiveProjectIdOnly(projectKey);
props.openMultiRunLauncher();
}}
onRenameStart={() => props.openProjectEditDialog(projectKey)}
onClose={() => props.removeProject(projectKey)}
sentinelRef={(el) => { props.projectHeaderSentinelRefs.current.set(projectKey, el); }}
@@ -18,7 +18,6 @@ import {
RiNodeTree,
RiPencilAiLine,
} from '@remixicon/react';
import { ArrowsMerge } from '@/components/icons/ArrowsMerge';
import { cn } from '@/lib/utils';
import { PROJECT_COLOR_MAP, PROJECT_ICON_MAP, getProjectIconImageUrl } from '@/lib/projectMeta';
import { useThemeSystem } from '@/contexts/useThemeSystem';
@@ -43,7 +42,6 @@ export interface SortableProjectItemProps {
onHoverChange: (hovered: boolean) => void;
onNewSession: () => void;
onNewWorktreeSession?: () => void;
onOpenMultiRunLauncher: () => void;
onRenameStart: () => void;
onClose: () => void;
sentinelRef: (el: HTMLDivElement | null) => void;
@@ -79,7 +77,6 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
onHoverChange,
onNewSession,
onNewWorktreeSession,
onOpenMultiRunLauncher,
onRenameStart,
onClose,
sentinelRef,
@@ -267,12 +264,6 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
New Session
</DropdownMenuItem>
)}
{showCreateButtons && isRepo && !hideDirectoryControls && (
<DropdownMenuItem onClick={onOpenMultiRunLauncher}>
<ArrowsMerge className="mr-1.5 h-4 w-4" />
New Multi-Run
</DropdownMenuItem>
)}
<DropdownMenuItem onClick={onRenameStart}>
<RiPencilAiLine className="mr-1.5 h-4 w-4" />
Rename