@@ -100,6 +100,26 @@ const buildChatItemsMock = vi.hoisted(() =>
100100 return [ ] ;
101101 } ) ,
102102) ;
103+ const renderMessageGroupMock = vi . hoisted ( ( ) =>
104+ vi . fn ( ( group : { messages : Array < { message : unknown } > } ) => {
105+ const element = document . createElement ( "div" ) ;
106+ element . className = "chat-group" ;
107+ element . textContent = group . messages
108+ . map ( ( { message } ) => {
109+ if ( typeof message === "object" && message !== null && "content" in message ) {
110+ const content = ( message as { content ?: unknown } ) . content ;
111+ if ( typeof content === "string" ) {
112+ return content ;
113+ }
114+ return content == null ? "" : JSON . stringify ( content ) ;
115+ }
116+ return String ( message ) ;
117+ } )
118+ . join ( "\n" ) ;
119+ return element ;
120+ } ) ,
121+ ) ;
122+ const assistantAttachmentRenderVersionMock = vi . hoisted ( ( ) => ( { value : 0 } ) ) ;
103123
104124function requireFirstAttachmentsChange (
105125 onAttachmentsChange : ReturnType < typeof vi . fn > ,
@@ -124,23 +144,8 @@ vi.mock("../chat/build-chat-items.ts", () => ({
124144} ) ) ;
125145
126146vi . mock ( "../chat/grouped-render.ts" , ( ) => ( {
127- renderMessageGroup : ( group : { messages : Array < { message : unknown } > } ) => {
128- const element = document . createElement ( "div" ) ;
129- element . className = "chat-group" ;
130- element . textContent = group . messages
131- . map ( ( { message } ) => {
132- if ( typeof message === "object" && message !== null && "content" in message ) {
133- const content = ( message as { content ?: unknown } ) . content ;
134- if ( typeof content === "string" ) {
135- return content ;
136- }
137- return content == null ? "" : JSON . stringify ( content ) ;
138- }
139- return String ( message ) ;
140- } )
141- . join ( "\n" ) ;
142- return element ;
143- } ,
147+ getAssistantAttachmentAvailabilityRenderVersion : ( ) => assistantAttachmentRenderVersionMock . value ,
148+ renderMessageGroup : renderMessageGroupMock ,
144149 renderReadingIndicatorGroup : ( ) => {
145150 const element = document . createElement ( "div" ) ;
146151 element . className = "chat-reading-indicator" ;
@@ -490,84 +495,87 @@ function clickTalkSelectOption(container: Element, name: string, value: string):
490495 option . dispatchEvent ( new MouseEvent ( "click" , { bubbles : true , cancelable : true } ) ) ;
491496}
492497
498+ function createChatProps (
499+ overrides : Partial < Parameters < typeof renderChat > [ 0 ] > = { } ,
500+ ) : Parameters < typeof renderChat > [ 0 ] {
501+ return {
502+ sessionKey : "main" ,
503+ onSessionKeyChange : ( ) => undefined ,
504+ thinkingLevel : null ,
505+ showThinking : false ,
506+ showToolCalls : true ,
507+ loading : false ,
508+ sending : false ,
509+ compactionStatus : null ,
510+ fallbackStatus : null ,
511+ messages : [ ] ,
512+ sideResult : null ,
513+ toolMessages : [ ] ,
514+ streamSegments : [ ] ,
515+ stream : null ,
516+ streamStartedAt : null ,
517+ assistantAvatarUrl : null ,
518+ draft : "" ,
519+ queue : [ ] ,
520+ realtimeTalkActive : false ,
521+ realtimeTalkStatus : "idle" ,
522+ realtimeTalkDetail : null ,
523+ realtimeTalkTranscript : null ,
524+ connected : true ,
525+ canSend : true ,
526+ disabledReason : null ,
527+ error : null ,
528+ sessions : null ,
529+ sidebarOpen : false ,
530+ sidebarContent : null ,
531+ sidebarError : null ,
532+ splitRatio : 0.6 ,
533+ canvasPluginSurfaceUrl : null ,
534+ embedSandboxMode : "scripts" ,
535+ allowExternalEmbedUrls : false ,
536+ assistantName : "Val" ,
537+ assistantAvatar : null ,
538+ userName : null ,
539+ userAvatar : null ,
540+ localMediaPreviewRoots : [ ] ,
541+ assistantAttachmentAuthToken : null ,
542+ autoExpandToolCalls : false ,
543+ attachments : [ ] ,
544+ onAttachmentsChange : ( ) => undefined ,
545+ showNewMessages : false ,
546+ onScrollToBottom : ( ) => undefined ,
547+ onRefresh : ( ) => undefined ,
548+ getDraft : ( ) => "" ,
549+ onDraftChange : ( ) => undefined ,
550+ onRequestUpdate : ( ) => undefined ,
551+ onSend : ( ) => undefined ,
552+ onCompact : ( ) => undefined ,
553+ onToggleRealtimeTalk : ( ) => undefined ,
554+ onDismissError : ( ) => undefined ,
555+ onAbort : ( ) => undefined ,
556+ onQueueRemove : ( ) => undefined ,
557+ onQueueSteer : ( ) => undefined ,
558+ onDismissSideResult : ( ) => undefined ,
559+ onNewSession : ( ) => undefined ,
560+ onClearHistory : ( ) => undefined ,
561+ onOpenSessionCheckpoints : ( ) => undefined ,
562+ agentsList : null ,
563+ currentAgentId : "main" ,
564+ onAgentChange : ( ) => undefined ,
565+ onNavigateToAgent : ( ) => undefined ,
566+ onSessionSelect : ( ) => undefined ,
567+ onOpenSidebar : ( ) => undefined ,
568+ onCloseSidebar : ( ) => undefined ,
569+ onSplitRatioChange : ( ) => undefined ,
570+ onChatScroll : ( ) => undefined ,
571+ basePath : "" ,
572+ ...overrides ,
573+ } ;
574+ }
575+
493576function renderChatView ( overrides : Partial < Parameters < typeof renderChat > [ 0 ] > = { } ) {
494577 const container = document . createElement ( "div" ) ;
495- render (
496- renderChat ( {
497- sessionKey : "main" ,
498- onSessionKeyChange : ( ) => undefined ,
499- thinkingLevel : null ,
500- showThinking : false ,
501- showToolCalls : true ,
502- loading : false ,
503- sending : false ,
504- compactionStatus : null ,
505- fallbackStatus : null ,
506- messages : [ ] ,
507- sideResult : null ,
508- toolMessages : [ ] ,
509- streamSegments : [ ] ,
510- stream : null ,
511- streamStartedAt : null ,
512- assistantAvatarUrl : null ,
513- draft : "" ,
514- queue : [ ] ,
515- realtimeTalkActive : false ,
516- realtimeTalkStatus : "idle" ,
517- realtimeTalkDetail : null ,
518- realtimeTalkTranscript : null ,
519- connected : true ,
520- canSend : true ,
521- disabledReason : null ,
522- error : null ,
523- sessions : null ,
524- sidebarOpen : false ,
525- sidebarContent : null ,
526- sidebarError : null ,
527- splitRatio : 0.6 ,
528- canvasPluginSurfaceUrl : null ,
529- embedSandboxMode : "scripts" ,
530- allowExternalEmbedUrls : false ,
531- assistantName : "Val" ,
532- assistantAvatar : null ,
533- userName : null ,
534- userAvatar : null ,
535- localMediaPreviewRoots : [ ] ,
536- assistantAttachmentAuthToken : null ,
537- autoExpandToolCalls : false ,
538- attachments : [ ] ,
539- onAttachmentsChange : ( ) => undefined ,
540- showNewMessages : false ,
541- onScrollToBottom : ( ) => undefined ,
542- onRefresh : ( ) => undefined ,
543- getDraft : ( ) => "" ,
544- onDraftChange : ( ) => undefined ,
545- onRequestUpdate : ( ) => undefined ,
546- onSend : ( ) => undefined ,
547- onCompact : ( ) => undefined ,
548- onToggleRealtimeTalk : ( ) => undefined ,
549- onDismissError : ( ) => undefined ,
550- onAbort : ( ) => undefined ,
551- onQueueRemove : ( ) => undefined ,
552- onQueueSteer : ( ) => undefined ,
553- onDismissSideResult : ( ) => undefined ,
554- onNewSession : ( ) => undefined ,
555- onClearHistory : ( ) => undefined ,
556- onOpenSessionCheckpoints : ( ) => undefined ,
557- agentsList : null ,
558- currentAgentId : "main" ,
559- onAgentChange : ( ) => undefined ,
560- onNavigateToAgent : ( ) => undefined ,
561- onSessionSelect : ( ) => undefined ,
562- onOpenSidebar : ( ) => undefined ,
563- onCloseSidebar : ( ) => undefined ,
564- onSplitRatioChange : ( ) => undefined ,
565- onChatScroll : ( ) => undefined ,
566- basePath : "" ,
567- ...overrides ,
568- } ) ,
569- container ,
570- ) ;
578+ render ( renderChat ( createChatProps ( overrides ) ) , container ) ;
571579 return container ;
572580}
573581
@@ -673,6 +681,8 @@ describe("chat composer workbench", () => {
673681afterEach ( ( ) => {
674682 vi . useRealTimers ( ) ;
675683 buildChatItemsMock . mockClear ( ) ;
684+ renderMessageGroupMock . mockClear ( ) ;
685+ assistantAttachmentRenderVersionMock . value = 0 ;
676686 loadSessionsMock . mockClear ( ) ;
677687 refreshVisibleToolsEffectiveForCurrentSessionMock . mockClear ( ) ;
678688 resetChatViewState ( ) ;
@@ -694,6 +704,51 @@ describe("chat transcript rendering cache", () => {
694704 expect ( buildChatItemsMock ) . toHaveBeenCalledTimes ( 1 ) ;
695705 } ) ;
696706
707+ it ( "does not rerender transcript groups for draft-only rerenders" , ( ) => {
708+ const messages = [ { role : "assistant" , content : "ready" } ] ;
709+ const toolMessages : unknown [ ] = [ ] ;
710+ const streamSegments : Array < { text : string ; ts : number } > = [ ] ;
711+ const queue : ChatQueueItem [ ] = [ ] ;
712+ const container = document . createElement ( "div" ) ;
713+
714+ render (
715+ renderChat ( createChatProps ( { messages, toolMessages, streamSegments, queue } ) ) ,
716+ container ,
717+ ) ;
718+ render (
719+ renderChat ( createChatProps ( { messages, toolMessages, streamSegments, queue, draft : "h" } ) ) ,
720+ container ,
721+ ) ;
722+ render (
723+ renderChat (
724+ createChatProps ( { messages, toolMessages, streamSegments, queue, draft : "hello" } ) ,
725+ ) ,
726+ container ,
727+ ) ;
728+
729+ expect ( renderMessageGroupMock ) . toHaveBeenCalledTimes ( 1 ) ;
730+ } ) ;
731+
732+ it ( "rerenders transcript groups when assistant attachment availability changes" , ( ) => {
733+ const messages = [ { role : "assistant" , content : "ready" } ] ;
734+ const toolMessages : unknown [ ] = [ ] ;
735+ const streamSegments : Array < { text : string ; ts : number } > = [ ] ;
736+ const queue : ChatQueueItem [ ] = [ ] ;
737+ const container = document . createElement ( "div" ) ;
738+
739+ render (
740+ renderChat ( createChatProps ( { messages, toolMessages, streamSegments, queue } ) ) ,
741+ container ,
742+ ) ;
743+ assistantAttachmentRenderVersionMock . value += 1 ;
744+ render (
745+ renderChat ( createChatProps ( { messages, toolMessages, streamSegments, queue, draft : "h" } ) ) ,
746+ container ,
747+ ) ;
748+
749+ expect ( renderMessageGroupMock ) . toHaveBeenCalledTimes ( 2 ) ;
750+ } ) ;
751+
697752 it ( "rebuilds transcript items when the transcript reference changes" , ( ) => {
698753 const toolMessages : unknown [ ] = [ ] ;
699754 const streamSegments : Array < { text : string ; ts : number } > = [ ] ;
0 commit comments