@@ -92,6 +92,7 @@ import { MOTHERSHIP_STREAM_REPLAY_HEADER } from '@/lib/mothership/constants'
9292import type { MothershipStreamV1EventEnvelope } from '@/lib/mothership/generated/mothership-stream-v1'
9393import { getChatResourceSelectionId } from '@/lib/mothership/resources/types'
9494import { collectCitedMessageSources } from '@/app/workspace/[workspaceId]/home/components/message-content/message-sources'
95+ import { ModelSelector } from '@/app/workspace/[workspaceId]/home/components/user-input/components/model-selector'
9596import {
9697 readQueuedSendHandoffState ,
9798 writeQueuedSendHandoffState ,
@@ -4701,6 +4702,94 @@ describe('useChat remount send recovery', () => {
47014702 ] )
47024703 } )
47034704
4705+ it ( 'keeps the new-chat effort across the composer swap of a first send that fails' , async ( ) => {
4706+ useMothershipEffortStore . getState ( ) . reset ( )
4707+ const post = Promise . withResolvers < Response > ( )
4708+ vi . stubGlobal ( 'fetch' , ( input : RequestInfo | URL , init ?: RequestInit ) => {
4709+ if ( String ( input ) !== '/api/mothership/chat' || init ?. method !== 'POST' ) {
4710+ return fetchStub ( input , init )
4711+ }
4712+ state . postBodies . push ( JSON . parse ( String ( init . body ) ) )
4713+ return post . promise
4714+ } )
4715+ ; ( globalThis as { IS_REACT_ACT_ENVIRONMENT ?: boolean } ) . IS_REACT_ACT_ENVIRONMENT = true
4716+ queryClient = new QueryClient ( { defaultOptions : { queries : { retry : false } } } )
4717+ const container = document . createElement ( 'div' )
4718+ const root = createRoot ( container )
4719+ mountedRoots . push ( root )
4720+ let result : ReturnType < typeof useChat > | undefined
4721+ /** Like home.tsx: the empty-state composer swaps for the chat view's once messages show. */
4722+ function HomeLike ( ) {
4723+ result = useChat ( 'ws-1' , undefined )
4724+ return result . messages . length > 0 ? (
4725+ < section key = 'chat' >
4726+ < ModelSelector />
4727+ </ section >
4728+ ) : (
4729+ < main key = 'empty' >
4730+ < ModelSelector />
4731+ </ main >
4732+ )
4733+ }
4734+ act ( ( ) => {
4735+ root . render (
4736+ < QueryClientProvider client = { queryClient } >
4737+ < HomeLike />
4738+ </ QueryClientProvider >
4739+ )
4740+ } )
4741+ const shownEffort = ( ) =>
4742+ container . querySelector ( '[aria-label="Reasoning effort"]' ) ?. getAttribute ( 'aria-description' )
4743+ act ( ( ) => useMothershipEffortStore . getState ( ) . setNewChatEffort ( 'low' ) )
4744+ expect ( shownEffort ( ) ) . toBe ( 'Low' )
4745+
4746+ await act ( async ( ) => {
4747+ void result ?. sendMessage ( 'Plan the launch' )
4748+ } )
4749+ await waitFor ( ( ) => state . postBodies . length === 1 )
4750+ expect ( state . postBodies [ 0 ] . effort ) . toBe ( 'low' )
4751+ expect ( container . querySelector ( 'section' ) ) . not . toBeNull ( )
4752+ expect ( shownEffort ( ) ) . toBe ( 'Low' )
4753+
4754+ await act ( async ( ) => {
4755+ post . reject ( new TypeError ( 'Failed to fetch' ) )
4756+ } )
4757+ await waitFor ( ( ) => container . querySelector ( 'main' ) !== null )
4758+
4759+ expect ( useMothershipEffortStore . getState ( ) . newChatEffort ) . toBe ( 'low' )
4760+ expect ( shownEffort ( ) ) . toBe ( 'Low' )
4761+ } )
4762+
4763+ it . each ( [ 'leaves the page' , 'opens another chat' ] as const ) (
4764+ 'drops an unsent new-chat effort when the surface %s' ,
4765+ ( leave ) => {
4766+ useMothershipEffortStore . getState ( ) . reset ( )
4767+ ; ( globalThis as { IS_REACT_ACT_ENVIRONMENT ?: boolean } ) . IS_REACT_ACT_ENVIRONMENT = true
4768+ queryClient = new QueryClient ( { defaultOptions : { queries : { retry : false } } } )
4769+ const root = createRoot ( document . createElement ( 'div' ) )
4770+ mountedRoots . push ( root )
4771+ function Surface ( { chatId } : { chatId ?: string } ) {
4772+ useChat ( 'ws-1' , chatId )
4773+ return null
4774+ }
4775+ const render = ( chatId ?: string ) =>
4776+ act ( ( ) =>
4777+ root . render (
4778+ < QueryClientProvider client = { queryClient } >
4779+ < Surface chatId = { chatId } />
4780+ </ QueryClientProvider >
4781+ )
4782+ )
4783+ render ( )
4784+ useMothershipEffortStore . getState ( ) . setNewChatEffort ( 'low' )
4785+
4786+ if ( leave === 'leaves the page' ) act ( ( ) => root . unmount ( ) )
4787+ else render ( 'chat-other' )
4788+
4789+ expect ( useMothershipEffortStore . getState ( ) . newChatEffort ) . toBeNull ( )
4790+ }
4791+ )
4792+
47044793 it ( 'loads the saved transcript once when its own stream completes' , async ( ) => {
47054794 const chatId = 'chat-own-completion'
47064795 const history : MothershipChatHistory = {
0 commit comments