@@ -385,7 +385,13 @@ const terminalContextIdListsEqual = (
385385) : boolean =>
386386 contexts . length === ids . length && contexts . every ( ( context , index ) => context . id === ids [ index ] ) ;
387387
388- const INTERACTION_MODE_CYCLE : readonly ProviderInteractionMode [ ] = [ "chat" , "code" , "plan" ] ;
388+ const INTERACTION_MODE_OPTIONS : readonly ProviderInteractionMode [ ] = [ "code" , "plan" ] ;
389+
390+ function normalizeVisibleInteractionMode (
391+ mode : ProviderInteractionMode | null | undefined ,
392+ ) : ProviderInteractionMode {
393+ return mode === "plan" ? "plan" : "code" ;
394+ }
389395
390396interface ChatViewProps {
391397 threadId : ThreadId ;
@@ -717,8 +723,9 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
717723 const activeThread = serverThread ?? localDraftThread ;
718724 const runtimeMode =
719725 composerDraft . runtimeMode ?? activeThread ?. runtimeMode ?? DEFAULT_RUNTIME_MODE ;
720- const interactionMode =
721- composerDraft . interactionMode ?? activeThread ?. interactionMode ?? DEFAULT_INTERACTION_MODE ;
726+ const interactionMode = normalizeVisibleInteractionMode (
727+ composerDraft . interactionMode ?? activeThread ?. interactionMode ?? DEFAULT_INTERACTION_MODE ,
728+ ) ;
722729 const isServerThread = serverThread !== undefined ;
723730 const isLocalDraftThread = ! isServerThread && localDraftThread !== undefined ;
724731 const canCheckoutPullRequestIntoThread = isLocalDraftThread ;
@@ -1432,13 +1439,6 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
14321439 label : "/plan" ,
14331440 description : "Switch this thread into plan mode" ,
14341441 } ,
1435- {
1436- id : "slash:chat" ,
1437- type : "slash-command" ,
1438- command : "chat" ,
1439- label : "/chat" ,
1440- description : "Switch this thread into chat mode" ,
1441- } ,
14421442 {
14431443 id : "slash:code" ,
14441444 type : "slash-command" ,
@@ -2175,8 +2175,8 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
21752175 ] ,
21762176 ) ;
21772177 const toggleInteractionMode = useCallback ( ( ) => {
2178- const idx = Math . max ( 0 , INTERACTION_MODE_CYCLE . indexOf ( interactionMode ) ) ;
2179- const next = INTERACTION_MODE_CYCLE [ ( idx + 1 ) % INTERACTION_MODE_CYCLE . length ] ! ;
2178+ const idx = Math . max ( 0 , INTERACTION_MODE_OPTIONS . indexOf ( interactionMode ) ) ;
2179+ const next = INTERACTION_MODE_OPTIONS [ ( idx + 1 ) % INTERACTION_MODE_OPTIONS . length ] ! ;
21802180 handleInteractionModeChange ( next ) ;
21812181 } , [ handleInteractionModeChange , interactionMode ] ) ;
21822182 const toggleRuntimeMode = useCallback ( ( ) => {
@@ -4072,9 +4072,9 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
40724072 createdAt : messageCreatedAt ,
40734073 } ) ;
40744074 // Optimistically open the plan sidebar when implementing (not refining).
4075- // Chat/code mode here means the agent is executing the plan, which produces
4075+ // Code mode means the agent is executing the plan, which produces
40764076 // step-tracking activities that the sidebar will display.
4077- if ( nextInteractionMode === "chat" || nextInteractionMode === " code") {
4077+ if ( nextInteractionMode === "code" ) {
40784078 planSidebarDismissedForTurnRef . current = null ;
40794079 setPlanSidebarOpen ( true ) ;
40804080 }
@@ -5353,23 +5353,50 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
53535353 className = "mx-0.5 hidden h-4 sm:block"
53545354 />
53555355
5356- < Button
5357- variant = "ghost"
5358- className = "shrink-0 whitespace-nowrap px-2 text-muted-foreground/70 hover:text-foreground/80 sm:px-3"
5359- size = "sm"
5360- type = "button"
5361- onClick = { toggleInteractionMode }
5362- title = "Cycle interaction mode: Chat → Code → Plan"
5356+ < div
5357+ className = "inline-flex shrink-0 items-center gap-1 rounded-xl border border-border/70 bg-card/80 p-1 shadow-[inset_0_1px_0_hsl(0_0%_100%/0.03)]"
5358+ aria-label = "Thread mode"
5359+ role = "group"
53635360 >
5364- < BotIcon />
5365- < span className = "sr-only sm:not-sr-only" >
5366- { interactionMode === "plan"
5367- ? "Plan"
5368- : interactionMode === "code"
5369- ? "Code"
5370- : "Chat" }
5371- </ span >
5372- </ Button >
5361+ < Button
5362+ variant = { interactionMode === "code" ? "secondary" : "ghost" }
5363+ className = { cn (
5364+ "h-7 gap-1.5 rounded-lg px-2.5 text-xs sm:h-8 sm:px-3" ,
5365+ interactionMode === "code"
5366+ ? "bg-foreground text-background hover:bg-foreground/90 hover:text-background"
5367+ : "text-muted-foreground hover:text-foreground" ,
5368+ ) }
5369+ size = "sm"
5370+ type = "button"
5371+ aria-pressed = { interactionMode === "code" }
5372+ aria-label = "Code mode"
5373+ data-testid = "thread-mode-code"
5374+ title = "Code mode"
5375+ onClick = { ( ) => handleInteractionModeChange ( "code" ) }
5376+ >
5377+ < BotIcon className = "size-3.5" />
5378+ < span > Code</ span >
5379+ </ Button >
5380+ < Button
5381+ variant = { interactionMode === "plan" ? "secondary" : "ghost" }
5382+ className = { cn (
5383+ "h-7 gap-1.5 rounded-lg px-2.5 text-xs sm:h-8 sm:px-3" ,
5384+ interactionMode === "plan"
5385+ ? "bg-blue-500/14 text-blue-200 ring-1 ring-inset ring-blue-400/40 hover:bg-blue-500/18 hover:text-blue-100"
5386+ : "text-muted-foreground hover:text-foreground" ,
5387+ ) }
5388+ size = "sm"
5389+ type = "button"
5390+ aria-pressed = { interactionMode === "plan" }
5391+ aria-label = "Plan mode"
5392+ data-testid = "thread-mode-plan"
5393+ title = "Plan mode"
5394+ onClick = { ( ) => handleInteractionModeChange ( "plan" ) }
5395+ >
5396+ < ListTodoIcon className = "size-3.5" />
5397+ < span > Plan</ span >
5398+ </ Button >
5399+ </ div >
53735400
53745401 < Separator
53755402 orientation = "vertical"
0 commit comments