@@ -47,9 +47,10 @@ import { isElectron } from "../env";
4747import { ensureLocalApi , readLocalApi } from "../localApi" ;
4848import {
4949 closeRightPanelSearchParams ,
50+ isDraftSourceControlPanelOpen ,
5051 isSourceControlPanelOpen ,
5152 parseDiffRouteSearch ,
52- preserveRightPanelSearchParamsForNavigation ,
53+ preserveRightPanelSearchParamsForDraftNavigation ,
5354 stripRightPanelSearchParams ,
5455} from "../diffRouteSearch" ;
5556import {
@@ -190,6 +191,7 @@ import { getComposerProviderState } from "./chat/composerProviderState";
190191import { ExpandedImageDialog } from "./chat/ExpandedImageDialog" ;
191192import { PullRequestThreadDialog } from "./PullRequestThreadDialog" ;
192193import { MessagesTimeline , type TimelineProposedPlanState } from "./chat/MessagesTimeline" ;
194+ import { DraftEmptyState } from "./chat/DraftEmptyState" ;
193195import { ProviderModelPicker } from "./chat/ProviderModelPicker" ;
194196import { ChatHeader , type ForkHeaderContext } from "./chat/ChatHeader" ;
195197import type { ThreadBackgroundRunItem } from "./chat/ThreadActivityPopover" ;
@@ -1261,19 +1263,17 @@ export default function ChatView(props: ChatViewProps) {
12611263 composerInteractionMode ?? activeThread ?. interactionMode ?? DEFAULT_INTERACTION_MODE ;
12621264 const isLocalDraftThread = ! isServerThread && localDraftThread !== undefined ;
12631265 const canCheckoutPullRequestIntoThread = isLocalDraftThread ;
1264- const sourceControlOpen = isSourceControlPanelOpen ( rawSearch , {
1265- defaultOpen : ! shouldUseRightPanelSheet ,
1266- } ) ;
1266+ // Mirror the owning route's panel default: server threads open on wide
1267+ // viewports, drafts stay closed until explicitly opened.
1268+ const sourceControlOpen =
1269+ routeKind === "server"
1270+ ? isSourceControlPanelOpen ( rawSearch , { defaultOpen : ! shouldUseRightPanelSheet } )
1271+ : isDraftSourceControlPanelOpen ( rawSearch ) ;
12671272 // The diff panel is a drill-in of source control, so the header toggle
12681273 // treats the right panel as one unit: it stays pressed while a diff is
12691274 // open and pressing it closes the whole panel.
12701275 const diffPanelOpen = rawSearch . diff === "1" ;
12711276 const rightPanelEngaged = sourceControlOpen || diffPanelOpen ;
1272- const preserveRightPanelSearchForDraftNavigation = useCallback (
1273- ( previous : Record < string , unknown > ) =>
1274- preserveRightPanelSearchParamsForNavigation ( previous , { sourceControlOpen } ) ,
1275- [ sourceControlOpen ] ,
1276- ) ;
12771277 const activeThreadId = activeThread ?. id ?? null ;
12781278 const activeThreadRef = useMemo (
12791279 ( ) => ( activeThread ? scopeThreadRef ( activeThread . environmentId , activeThread . id ) : null ) ,
@@ -1323,6 +1323,34 @@ export default function ChatView(props: ChatViewProps) {
13231323 // General Chat threads run in a hidden scratch workspace: source-control,
13241324 // scripts, and open-in affordances stay hidden even though a project exists.
13251325 const isGeneralChatThread = activeProject ?. kind === "general-chat" ;
1326+ const insertDraftStarterPrompt = useCallback (
1327+ ( text : string ) => {
1328+ setComposerDraftPrompt ( composerDraftTarget , text ) ;
1329+ window . requestAnimationFrame ( ( ) => {
1330+ composerRef . current ?. focusAtEnd ( ) ;
1331+ } ) ;
1332+ } ,
1333+ [ composerDraftTarget , composerRef , setComposerDraftPrompt ] ,
1334+ ) ;
1335+ const draftTimelineEmptyState = useMemo (
1336+ ( ) =>
1337+ isLocalDraftThread && draftThread ? (
1338+ < DraftEmptyState
1339+ currentProjectRef = { scopeProjectRef ( draftThread . environmentId , draftThread . projectId ) }
1340+ currentProjectName = { activeProject ?. name ?? null }
1341+ isGeneralChat = { isGeneralChatThread }
1342+ onInsertPrompt = { insertDraftStarterPrompt }
1343+ />
1344+ ) : undefined ,
1345+ [
1346+ activeProject ?. name ,
1347+ draftThread ?. environmentId ,
1348+ draftThread ?. projectId ,
1349+ insertDraftStarterPrompt ,
1350+ isGeneralChatThread ,
1351+ isLocalDraftThread ,
1352+ ] ,
1353+ ) ;
13261354
13271355 const shouldRetainThreadDetailSubscription = routeKind === "server" || serverThread !== undefined ;
13281356 useEffect ( ( ) => {
@@ -1503,7 +1531,7 @@ export default function ChatView(props: ChatViewProps) {
15031531 await navigate ( {
15041532 to : "/draft/$draftId" ,
15051533 params : buildDraftThreadRouteParams ( storedDraftSession . draftId ) ,
1506- search : preserveRightPanelSearchForDraftNavigation ,
1534+ search : preserveRightPanelSearchParamsForDraftNavigation ,
15071535 } ) ;
15081536 }
15091537 return storedDraftSession . threadId ;
@@ -1538,7 +1566,7 @@ export default function ChatView(props: ChatViewProps) {
15381566 await navigate ( {
15391567 to : "/draft/$draftId" ,
15401568 params : buildDraftThreadRouteParams ( nextDraftId ) ,
1541- search : preserveRightPanelSearchForDraftNavigation ,
1569+ search : preserveRightPanelSearchParamsForDraftNavigation ,
15421570 } ) ;
15431571 return nextThreadId ;
15441572 } ,
@@ -1549,7 +1577,6 @@ export default function ChatView(props: ChatViewProps) {
15491577 getDraftSessionByLogicalProjectKey ,
15501578 isServerThread ,
15511579 navigate ,
1552- preserveRightPanelSearchForDraftNavigation ,
15531580 projectGroupingSettings ,
15541581 routeKind ,
15551582 setDraftThreadContext ,
@@ -3764,9 +3791,13 @@ export default function ChatView(props: ChatViewProps) {
37643791 setIsRevertingCheckpoint ( false ) ;
37653792 } , [ activeThread ?. id ] ) ;
37663793
3794+ // Pristine drafts (the home surface) open without stealing focus; the
3795+ // composer takes focus once a conversation exists or via an explicit action.
3796+ const skipEntryComposerFocus = isLocalDraftThread && ( activeThread ?. messages . length ?? 0 ) === 0 ;
37673797 useEffect ( ( ) => {
37683798 if (
37693799 ! activeThread ?. id ||
3800+ skipEntryComposerFocus ||
37703801 timelineSearchTargetRef . current ||
37713802 terminalState . terminalOpen ||
37723803 isCoarsePointer
@@ -3779,7 +3810,13 @@ export default function ChatView(props: ChatViewProps) {
37793810 return ( ) => {
37803811 window . cancelAnimationFrame ( frame ) ;
37813812 } ;
3782- } , [ activeThread ?. id , focusComposer , isCoarsePointer , terminalState . terminalOpen ] ) ;
3813+ } , [
3814+ activeThread ?. id ,
3815+ focusComposer ,
3816+ isCoarsePointer ,
3817+ skipEntryComposerFocus ,
3818+ terminalState . terminalOpen ,
3819+ ] ) ;
37833820
37843821 useEffect ( ( ) => {
37853822 if ( ! activeThread ?. id ) return ;
@@ -6058,6 +6095,7 @@ export default function ChatView(props: ChatViewProps) {
60586095 { /* Messages — LegendList handles virtualization and scrolling internally */ }
60596096 < MessagesTimeline
60606097 key = { activeThread . id }
6098+ emptyState = { draftTimelineEmptyState }
60616099 isWorking = { isWorking }
60626100 activeStatusLabel = { activeStatusLabel }
60636101 activeTurnInProgress = { isWorking || ! latestTurnSettled }
0 commit comments