@@ -563,7 +563,6 @@ function VerticalSessionTabs(props: {
563563 const width = ( ) => props . width ?? SESSION_SIDEBAR_WIDTH
564564 const compact = createMemo ( ( ) => width ( ) < SESSION_TABS_COMPACT_BREAKPOINT )
565565 const tooltipWidth = ( ) => Math . min ( 54 , dimensions ( ) . width - width ( ) )
566- const stride = ( ) => ( compact ( ) ? 2 : 3 )
567566 const unreadColor = ( ) => theme . hue . accent [ 200 ]
568567 const activeNumber = ( ) => theme . hue . interactive [ 200 ]
569568 const idleNumber = ( ) => tint ( theme . text . formfield . base , background ( ) , 0.55 )
@@ -604,14 +603,25 @@ function VerticalSessionTabs(props: {
604603 return moveSessionTab ( tabs . tabs ( ) , pending . sessionID , pending . index )
605604 } )
606605 const items = ordered
606+ // Wrapped titles make tab heights vary, so positions come from the laid-out boxes.
607+ const boxes = new Map < string , BoxRenderable > ( )
608+ let addRow : BoxRenderable | undefined
609+ const indexAt = ( y : number ) => {
610+ const index = items ( ) . findIndex ( ( tab ) => {
611+ const box = boxes . get ( tab . sessionID )
612+ return box !== undefined && y < box . screenY + box . height + 1
613+ } )
614+ return index === - 1 ? items ( ) . length - 1 : index
615+ }
607616 const highlightColor = createMemo ( ( ) =>
608617 tint ( background ( ) , actionHovered ( ) , actionHovered ( ) . a ) ,
609618 )
610619 const highlighted = ( sessionID : string | undefined ) =>
611620 sessionID !== undefined && ( activeID ( ) === sessionID || hovered ( ) === sessionID || dragging ( ) === sessionID )
612621 const addHighlighted = ( ) => newTab ( ) || addHovered ( )
613622 const belowHighlighted = createMemo ( ( ) => {
614- const tab = items ( ) [ Math . floor ( scrollTop ( ) / stride ( ) ) ]
623+ // Only the compact rail shows controls, and its tabs are a fixed two cells apart.
624+ const tab = items ( ) [ Math . floor ( scrollTop ( ) / 2 ) ]
615625 return tab ? highlighted ( tab . sessionID ) : addHighlighted ( )
616626 } )
617627 createEffect ( ( ) => {
@@ -659,11 +669,14 @@ function VerticalSessionTabs(props: {
659669 dimensions ( )
660670 const index = newTab ( ) ? items ( ) . length : items ( ) . findIndex ( ( tab ) => tab . sessionID === activeID ( ) )
661671 if ( index === - 1 ) return
662- const top = index * stride ( )
663- const height = compact ( ) ? 3 : newTab ( ) ? 1 : 2
664672 // Scroll after layout: newly opened tabs do not contribute to the scroll range yet.
665673 const reveal = ( ) => {
666674 if ( ! scroll ) return
675+ const box = newTab ( ) ? addRow : boxes . get ( activeID ( ) ! )
676+ if ( ! box ) return
677+ // Compact tabs also reveal the half rows drawn above and below them.
678+ const top = box . screenY - scroll . content . screenY - ( compact ( ) ? 1 : 0 )
679+ const height = compact ( ) ? 3 : box . height
667680 if ( top < scroll . scrollTop ) return scroll . scrollTo ( top )
668681 if ( top + height > scroll . scrollTop + scroll . viewport . height ) {
669682 scroll . scrollTo ( top + height - scroll . viewport . height )
@@ -693,10 +706,7 @@ function VerticalSessionTabs(props: {
693706 0 ,
694707 Math . min (
695708 tabs . tabs ( ) . length - 1 ,
696- Math . floor (
697- ( event . y - ( scroll ?. viewport . screenY ?? rail . screenY + 1 ) - ( compact ( ) ? 1 : 0 ) + ( scroll ?. scrollTop ?? 0 ) ) /
698- stride ( ) ,
699- ) ,
709+ indexAt ( event . y ) ,
700710 ) ,
701711 )
702712 const sourceIndex = items ( ) . findIndex ( ( item ) => item . sessionID === source )
@@ -752,21 +762,7 @@ function VerticalSessionTabs(props: {
752762 const hoveredTitleWidth = ( ) => Math . max ( 1 , restingTitleWidth ( ) - 1 )
753763 const titleWidth = ( ) => ( hovered ( ) === tab . sessionID ? hoveredTitleWidth ( ) : restingTitleWidth ( ) )
754764 const title = ( ) => ( props . controller ? undefined : session ( ) ?. title ) ?? tab . title ?? "Untitled session"
755- const scrolling = ( ) => marquee . active ( ) === tab . sessionID
756- const visibleTitleParts = createMemo ( ( ) =>
757- scrolling ( )
758- ? marqueeTextParts ( title ( ) , titleWidth ( ) , marquee . offset ( ) )
759- : Locale . graphemes ( Locale . takeWidth ( title ( ) , titleWidth ( ) ) ) . map ( ( value ) => ( {
760- value,
761- separator : false ,
762- } ) ) ,
763- )
764- const visibleTitle = createMemo ( ( ) =>
765- visibleTitleParts ( )
766- . map ( ( part ) => part . value )
767- . join ( "" ) ,
768- )
769- const titleFades = createMemo ( ( ) => marqueeOverflows ( title ( ) , titleWidth ( ) ) && titleWidth ( ) > FADE_WIDTH )
765+ const titleParts = createMemo ( ( ) => Locale . graphemes ( title ( ) ) )
770766 const tabDetail = createMemo ( ( ) => detail ( tab . sessionID ) )
771767 const visibleDetail = createMemo ( ( ) => Locale . takeWidth ( tabDetail ( ) , titleWidth ( ) ) )
772768 const visibleDetailParts = createMemo ( ( ) => Locale . graphemes ( visibleDetail ( ) ) )
@@ -861,33 +857,24 @@ function VerticalSessionTabs(props: {
861857 const separatorLowerColor = createMemo ( ( ) =>
862858 tint ( background ( ) , glowHue ( ) , 0.12 * glowLevel ( ) ) ,
863859 )
864- const titleColor = ( index : number , separator : boolean ) => {
860+ const titleColor = ( index : number ) => {
865861 const level = titleGlow . value ( ) . level
866- const color =
867- level === 0
868- ? foreground ( )
869- : glowTextColor ( foreground ( ) , glowColor ( ) , 1 + numberWidth ( ) + index , width ( ) , level )
870- const faded = titleFades ( )
871- ? fadeTitleColor (
872- color ,
873- pulseBackground ( ) ,
874- index ,
875- visibleTitleParts ( ) . length ,
876- scrolling ( ) ? marquee . leading ( ) : 0 ,
877- )
878- : color
879- return separator ? tint ( faded , pulseBackground ( ) , 0.55 ) : faded
862+ return level === 0
863+ ? foreground ( )
864+ : glowTextColor ( foreground ( ) , glowColor ( ) , 1 + numberWidth ( ) + index , width ( ) , level )
880865 }
866+ onCleanup ( ( ) => boxes . delete ( tab . sessionID ) )
881867 return (
882868 < box
883- height = { compact ( ) ? 1 : 2 }
869+ ref = { ( element : BoxRenderable ) => boxes . set ( tab . sessionID , element ) }
870+ height = { compact ( ) ? 1 : undefined }
884871 width = "100%"
885872 position = "relative"
886873 flexDirection = "column"
887874 backgroundColor = { tabBackground ( ) }
888875 onMouseOver = { ( event ) => {
889876 setHoverY ( event . y )
890- marquee . enter ( tab . sessionID , title ( ) , compact ( ) ? Infinity : hoveredTitleWidth ( ) )
877+ marquee . enter ( tab . sessionID , title ( ) , Infinity )
891878 } }
892879 onMouseOut = { ( ) => marquee . leave ( tab . sessionID ) }
893880 onMouseDown = { ( event ) => {
@@ -914,7 +901,7 @@ function VerticalSessionTabs(props: {
914901 return
915902 }
916903 didDrag = false
917- marquee . enter ( tab . sessionID , title ( ) , compact ( ) ? Infinity : hoveredTitleWidth ( ) )
904+ marquee . enter ( tab . sessionID , title ( ) , Infinity )
918905 setDragging ( tab . sessionID )
919906 } }
920907 >
@@ -1000,7 +987,7 @@ function VerticalSessionTabs(props: {
1000987 />
1001988 < Show when = { index ( ) === items ( ) . length - 1 } >
1002989 < TabPulse
1003- top = { 2 }
990+ bottom = { - 1 }
1004991 edge = "below"
1005992 enabled = { animations ( ) }
1006993 active = { runs ( ) }
@@ -1025,7 +1012,7 @@ function VerticalSessionTabs(props: {
10251012 backgroundColor = { background ( ) }
10261013 />
10271014 </ Show >
1028- < box height = { 1 } width = "100%" flexDirection = "row" position = "relative" >
1015+ < box width = "100%" flexDirection = "row" position = "relative" >
10291016 < TabPulse
10301017 enabled = { animations ( ) }
10311018 active = { runs ( ) }
@@ -1057,13 +1044,12 @@ function VerticalSessionTabs(props: {
10571044 attributes = { selected ( ) ? TextAttributes . BOLD : undefined }
10581045 />
10591046 < title_shimmer
1060- width = { titleWidth ( ) }
1061- height = { 1 }
1047+ width = { hoveredTitleWidth ( ) }
10621048 fg = { foreground ( ) }
10631049 rename = { { pending : status ( ) . renaming , title : title ( ) } }
10641050 enabled = { animations ( ) }
10651051 backdrop = { pulseBackground ( ) }
1066- wrapMode = "none "
1052+ wrapMode = "word "
10671053 selectable = { false }
10681054 attributes = {
10691055 status ( ) . renaming && ! animations ( )
@@ -1073,14 +1059,9 @@ function VerticalSessionTabs(props: {
10731059 : undefined
10741060 }
10751061 >
1076- < Show
1077- when = { scrolling ( ) || titleGlow . value ( ) . level > 0 || titleFades ( ) }
1078- fallback = { visibleTitle ( ) }
1079- >
1080- < Index each = { visibleTitleParts ( ) } >
1081- { ( part , index ) => (
1082- < span style = { { fg : titleColor ( index , part ( ) . separator ) } } > { part ( ) . value } </ span >
1083- ) }
1062+ < Show when = { titleGlow . value ( ) . level > 0 } fallback = { title ( ) } >
1063+ < Index each = { titleParts ( ) } >
1064+ { ( part , index ) => < span style = { { fg : titleColor ( index ) } } > { part ( ) } </ span > }
10841065 </ Index >
10851066 </ Show >
10861067 </ title_shimmer >
@@ -1145,6 +1126,7 @@ function VerticalSessionTabs(props: {
11451126 active new-session tab, instead of spawning a separate pseudo tab above itself. */ }
11461127 < Show when = { tabs . add || newTab ( ) } >
11471128 < box
1129+ ref = { ( element : BoxRenderable ) => ( addRow = element ) }
11481130 height = { 1 }
11491131 width = "100%"
11501132 position = "relative"
0 commit comments