@@ -8,6 +8,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
88import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
99import { Dialog } from "@opencode-ai/ui/dialog"
1010import { InlineInput } from "@opencode-ai/ui/inline-input"
11+ import { Spinner } from "@opencode-ai/ui/spinner"
1112import { SessionTurn } from "@opencode-ai/ui/session-turn"
1213import { ScrollView } from "@opencode-ai/ui/scroll-view"
1314import type { AssistantMessage , Message as MessageType , Part , TextPart , UserMessage } from "@opencode-ai/sdk/v2"
@@ -235,6 +236,40 @@ export function MessageTimeline(props: {
235236 if ( ! id ) return idle
236237 return sync . data . session_status [ id ] ?? idle
237238 } )
239+ const working = createMemo ( ( ) => ! ! pending ( ) || sessionStatus ( ) . type !== "idle" )
240+
241+ const [ slot , setSlot ] = createStore ( {
242+ open : false ,
243+ show : false ,
244+ fade : false ,
245+ } )
246+
247+ let f : number | undefined
248+ const clear = ( ) => {
249+ if ( f !== undefined ) window . clearTimeout ( f )
250+ f = undefined
251+ }
252+
253+ onCleanup ( clear )
254+ createEffect (
255+ on (
256+ working ,
257+ ( on , prev ) => {
258+ clear ( )
259+ if ( on ) {
260+ setSlot ( { open : true , show : true , fade : false } )
261+ return
262+ }
263+ if ( prev ) {
264+ setSlot ( { open : false , show : true , fade : true } )
265+ f = window . setTimeout ( ( ) => setSlot ( { show : false , fade : false } ) , 260 )
266+ return
267+ }
268+ setSlot ( { open : false , show : false , fade : false } )
269+ } ,
270+ { defer : true } ,
271+ ) ,
272+ )
238273 const activeMessageID = createMemo ( ( ) => {
239274 const parentID = pending ( ) ?. parentID
240275 if ( parentID ) {
@@ -573,43 +608,64 @@ export function MessageTimeline(props: {
573608 aria-label = { language . t ( "common.goBack" ) }
574609 />
575610 </ Show >
576- < Show when = { titleValue ( ) || title . editing } >
577- < Show
578- when = { title . editing }
579- fallback = {
580- < h1
581- class = "text-14-medium text-text-strong truncate grow-1 min-w-0 pl-2"
582- onDblClick = { openTitleEditor }
583- >
584- { titleValue ( ) }
585- </ h1 >
586- }
611+ < div class = "flex items-center min-w-0 grow-1" >
612+ < div
613+ class = "shrink-0 flex items-center justify-center overflow-hidden transition-[width,margin] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]"
614+ style = { {
615+ width : slot . open ? "16px" : "0px" ,
616+ "margin-right" : slot . open ? "8px" : "0px" ,
617+ } }
618+ aria-hidden = "true"
587619 >
588- < InlineInput
589- ref = { ( el ) => {
590- titleRef = el
591- } }
592- value = { title . draft }
593- disabled = { title . saving }
594- class = "text-14-medium text-text-strong grow-1 min-w-0 pl-2 rounded-[6px]"
595- style = { { "--inline-input-shadow" : "var(--shadow-xs-border-select)" } }
596- onInput = { ( event ) => setTitle ( "draft" , event . currentTarget . value ) }
597- onKeyDown = { ( event ) => {
598- event . stopPropagation ( )
599- if ( event . key === "Enter" ) {
600- event . preventDefault ( )
601- void saveTitleEditor ( )
602- return
603- }
604- if ( event . key === "Escape" ) {
605- event . preventDefault ( )
606- closeTitleEditor ( )
607- }
608- } }
609- onBlur = { closeTitleEditor }
610- />
620+ < Show when = { slot . show } >
621+ < div
622+ class = "transition-opacity duration-200 ease-out"
623+ classList = { {
624+ "opacity-0" : slot . fade ,
625+ } }
626+ >
627+ < Spinner class = "size-4" style = { { color : "var(--icon-interactive-base)" } } />
628+ </ div >
629+ </ Show >
630+ </ div >
631+ < Show when = { titleValue ( ) || title . editing } >
632+ < Show
633+ when = { title . editing }
634+ fallback = {
635+ < h1
636+ class = "text-14-medium text-text-strong truncate grow-1 min-w-0"
637+ onDblClick = { openTitleEditor }
638+ >
639+ { titleValue ( ) }
640+ </ h1 >
641+ }
642+ >
643+ < InlineInput
644+ ref = { ( el ) => {
645+ titleRef = el
646+ } }
647+ value = { title . draft }
648+ disabled = { title . saving }
649+ class = "text-14-medium text-text-strong grow-1 min-w-0 rounded-[6px]"
650+ style = { { "--inline-input-shadow" : "var(--shadow-xs-border-select)" } }
651+ onInput = { ( event ) => setTitle ( "draft" , event . currentTarget . value ) }
652+ onKeyDown = { ( event ) => {
653+ event . stopPropagation ( )
654+ if ( event . key === "Enter" ) {
655+ event . preventDefault ( )
656+ void saveTitleEditor ( )
657+ return
658+ }
659+ if ( event . key === "Escape" ) {
660+ event . preventDefault ( )
661+ closeTitleEditor ( )
662+ }
663+ } }
664+ onBlur = { closeTitleEditor }
665+ />
666+ </ Show >
611667 </ Show >
612- </ Show >
668+ </ div >
613669 </ div >
614670 < Show when = { sessionID ( ) } >
615671 { ( id ) => (
0 commit comments