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
+1 -1
View File
@@ -592,7 +592,7 @@ export const Header: React.FC<HeaderProps> = ({
if (!state.newSessionDraft?.open) {
return '';
}
return normalize(state.newSessionDraft.directoryOverride ?? '');
return normalize(state.newSessionDraft.bootstrapPendingDirectory ?? state.newSessionDraft.directoryOverride ?? '');
});
const openDirectory = React.useMemo(() => {
@@ -23,7 +23,7 @@ import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
import { cn } from '@/lib/utils';
import { isDesktopShell } from '@/lib/desktop';
import { ChatView, PlanView, GitView, DiffView, TerminalView, FilesView, SettingsView, SettingsWindow } from '@/components/views';
import { ChatView, PlanView, GitView, DiffView, TerminalView, FilesView, SettingsView, SettingsWindow, MultiRunWindow } from '@/components/views';
// Mobile drawer width as screen percentage
const MOBILE_DRAWER_WIDTH_PERCENT = 85;
@@ -645,7 +645,7 @@ export const MainLayout: React.FC = () => {
setRightSidebarOpen,
}}>
{/* Mobile: header + drawer mode */}
{!(isSettingsDialogOpen || isMultiRunLauncherOpen) && <Header
{!isSettingsDialogOpen && <Header
onToggleLeftDrawer={() => {
if (isRightSidebarOpen) {
setRightSidebarOpen(false);
@@ -779,7 +779,7 @@ export const MainLayout: React.FC = () => {
<div
className={cn(
'flex flex-1 overflow-hidden relative',
(isSettingsDialogOpen || isMultiRunLauncherOpen) && 'hidden'
isSettingsDialogOpen && 'hidden'
)}
>
<main className="w-full h-full overflow-hidden bg-background relative">
@@ -791,25 +791,20 @@ export const MainLayout: React.FC = () => {
<ErrorBoundary>{secondaryView}</ErrorBoundary>
</div>
)}
{isMultiRunLauncherOpen && (
<div className="absolute inset-0 z-10 bg-background">
<ErrorBoundary>
<MultiRunLauncher
initialPrompt={multiRunLauncherPrefillPrompt}
onCreated={() => setMultiRunLauncherOpen(false)}
onCancel={() => setMultiRunLauncherOpen(false)}
/>
</ErrorBoundary>
</div>
)}
</main>
</div>
{/* Mobile multi-run launcher: full screen */}
{isMultiRunLauncherOpen && (
<div
className="absolute inset-0 z-10 bg-background"
style={{ paddingTop: 'var(--oc-safe-area-top, 0px)' }}
>
<ErrorBoundary>
<MultiRunLauncher
initialPrompt={multiRunLauncherPrefillPrompt}
onCreated={() => setMultiRunLauncherOpen(false)}
onCancel={() => setMultiRunLauncherOpen(false)}
/>
</ErrorBoundary>
</div>
)}
{/* Mobile settings: full screen */}
{isSettingsDialogOpen && (
<div
@@ -828,8 +823,7 @@ export const MainLayout: React.FC = () => {
'absolute inset-0 flex overflow-hidden',
isDesktopShellRuntime
? 'bg-[color:var(--sidebar-overlay-strong)] backdrop-blur supports-[backdrop-filter]:bg-[color:var(--sidebar-overlay-soft)]'
: 'bg-sidebar',
isMultiRunLauncherOpen && 'invisible'
: 'bg-sidebar'
)}>
{isSidebarOpen ? (
<>
@@ -951,18 +945,6 @@ export const MainLayout: React.FC = () => {
</RightSidebar>
</div>
{/* Multi-Run Launcher: replaces tabs content only */}
{isMultiRunLauncherOpen && (
<div className={cn('absolute inset-0 z-10 bg-background')}>
<ErrorBoundary>
<MultiRunLauncher
initialPrompt={multiRunLauncherPrefillPrompt}
onCreated={() => setMultiRunLauncherOpen(false)}
onCancel={() => setMultiRunLauncherOpen(false)}
/>
</ErrorBoundary>
</div>
)}
</div>
{/* Desktop settings: windowed dialog with blur */}
@@ -970,6 +952,11 @@ export const MainLayout: React.FC = () => {
open={isSettingsDialogOpen}
onOpenChange={setSettingsDialogOpen}
/>
<MultiRunWindow
open={isMultiRunLauncherOpen}
onOpenChange={setMultiRunLauncherOpen}
initialPrompt={multiRunLauncherPrefillPrompt}
/>
</>
)}