1- import { createEffect , createMemo , createResource , For , Match , Show , startTransition , Switch , untrack } from "solid-js"
1+ import {
2+ createEffect ,
3+ createMemo ,
4+ createResource ,
5+ createSignal ,
6+ For ,
7+ Match ,
8+ onMount ,
9+ Show ,
10+ startTransition ,
11+ Switch ,
12+ untrack ,
13+ } from "solid-js"
214import { createStore } from "solid-js/store"
315import { useLocation , useMatch , useNavigate , useParams } from "@solidjs/router"
416import { IconButton } from "@opencode-ai/ui/icon-button"
@@ -230,7 +242,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
230242 : undefined ,
231243 "align-self" : electronWindows ( ) ? "flex-start" : undefined ,
232244 } }
233- data-tauri-drag-region
245+ // data-tauri-drag-region
234246 onMouseDown = { drag }
235247 onDblClick = { maximize }
236248 >
@@ -393,9 +405,16 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
393405 ] . filter ( ( v ) => v !== undefined )
394406 } )
395407
408+ const [ tabsAreOverflowing , setTabsAreOverflowing ] = createSignal ( false )
409+ let tabScrollRef ! : HTMLDivElement
410+
411+ function refreshTabsAreOverflowing ( ) {
412+ setTabsAreOverflowing ( tabScrollRef . scrollWidth > tabScrollRef . clientWidth )
413+ }
414+
396415 return (
397416 < div
398- class = "h-full flex-1 flex flex-row items-center gap-1.5 pr-3 pt-2"
417+ class = "h-full flex-1 overflow-hidden flex flex-row items-center gap-1.5 pr-3 pt-2"
399418 classList = { {
400419 "pl-2" : mac ( ) ,
401420 "pl-4" : ! mac ( ) ,
@@ -410,60 +429,86 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
410429 size = "large"
411430 as = "a"
412431 href = "/"
413- class = "!w-9"
432+ class = "!w-9 shrink-0 "
414433 icon = { < IconV2 name = "grid-plus" /> }
415434 state = { ! ! homeMatch ( ) ? "pressed" : undefined }
416435 />
417436
418- < div class = "flex min-w-0 flex-1 flex-row items-center gap-1.5 overflow-hidden" >
419- < div class = "flex min-w-0 flex-row items-center gap-1.5 overflow-hidden" >
420- < For each = { tabsStore } >
421- { ( tab , i ) => (
422- < >
423- { i ( ) !== 0 && (
424- < div class = "w-[1.5px] h-3 shrink-0 rounded-full bg-[var(--v2-background-bg-layer-02)]" />
425- ) }
426- < TabNavItem
427- href = { tabHref ( tab ) }
428- server = { tab . server }
429- directory = { decode64 ( tab . dirBase64 ) ! }
430- sessionId = { tab . sessionId }
431- onNavigate = { ( ) => navigateTab ( tab ) }
432- onClose = { ( ) => tabsStoreActions . removeTab ( i ( ) ) }
433- active = { currentTab ( ) === tab }
434- activeServer = { tab . server === server . key }
435- />
436- </ >
437- ) }
437+ < div class = "flex min-w-0 flex-row items-center gap-1.5 overflow-x-auto no-scrollbar" ref = { tabScrollRef } >
438+ < div class = "flex min-w-0 flex-row items-center gap-1.5" >
439+ < For each = { [ ...tabsStore , ...tabsStore , ...tabsStore ] } >
440+ { ( tab , i ) => {
441+ let ref ! : HTMLDivElement
442+
443+ onMount ( ( ) => {
444+ refreshTabsAreOverflowing ( )
445+ } )
446+
447+ return (
448+ < >
449+ { i ( ) !== 0 && (
450+ < div class = "w-[1.5px] h-3 shrink-0 rounded-full bg-[var(--v2-background-bg-layer-02)]" />
451+ ) }
452+ < TabNavItem
453+ ref = { ref }
454+ href = { tabHref ( tab ) }
455+ server = { tab . server }
456+ directory = { decode64 ( tab . dirBase64 ) ! }
457+ sessionId = { tab . sessionId }
458+ onNavigate = { ( ) => {
459+ navigateTab ( tab )
460+
461+ ref . scrollIntoView ( { behavior : "instant" } )
462+ } }
463+ onClose = { ( ) => tabsStoreActions . removeTab ( i ( ) ) }
464+ active = { currentTab ( ) === tab }
465+ activeServer = { tab . server === server . key }
466+ forceTruncate = { tabsAreOverflowing ( ) }
467+ />
468+ </ >
469+ )
470+ } }
438471 </ For >
439- </ div >
440- < Show
441- when = { creating ( ) && params . dir }
442- fallback = {
443- < IconButtonV2
444- type = "button"
445- variant = "ghost-muted"
446- size = "large"
447- class = "shrink-0"
448- icon = { < IconV2 name = "plus" /> }
449- as = "a"
450- href = { newSessionHref ( ) }
451- aria-label = { language . t ( "command.session.new" ) }
452- />
453- }
454- >
455- < NewSessionTabItem
456- href = { `/ ${ params . dir } /session` }
457- title = { language . t ( "command.session.new" ) }
458- onClose = { ( ) => {
459- const tab = tabsStore . at ( - 1 )
460- if ( tab ) navigateTab ( tab )
461- else navigate ( "/" )
472+ < Show when = { creating ( ) && params . dir } >
473+ { ( _ ) => {
474+ let ref ! : HTMLDivElement
475+
476+ onMount ( ( ) => {
477+ ref . scrollIntoView ( { behavior : "instant" } )
478+ } )
479+
480+ return (
481+ < >
482+ < div class = "w-[1.5px] h-3 shrink-0 rounded-full bg-[var(--v2-background-bg-layer-02)]" />
483+ < NewSessionTabItem
484+ ref = { ref }
485+ href = { `/ ${ params . dir } /session` }
486+ title = { language . t ( "command.session.new" ) }
487+ onClose = { ( ) => {
488+ const tab = tabsStore . at ( - 1 )
489+ if ( tab ) navigateTab ( tab )
490+ else navigate ( "/" )
491+ } }
492+ />
493+ </ >
494+ )
462495 } }
463- />
464- </ Show >
465- < div class = "min-w-0 flex-1" />
496+ </ Show >
497+ </ div >
466498 </ div >
499+ < Show when = { ! ( creating ( ) && params . dir ) } >
500+ < IconButtonV2
501+ type = "button"
502+ variant = "ghost-muted"
503+ size = "large"
504+ class = "shrink-0"
505+ icon = { < IconV2 name = "plus" /> }
506+ as = "a"
507+ href = { newSessionHref ( ) }
508+ aria-label = { language . t ( "command.session.new" ) }
509+ />
510+ </ Show >
511+ < div class = "flex-1" />
467512 < TitlebarV2Right state = { v2RightState ( ) } />
468513 < Show when = { windows ( ) && ! electronWindows ( ) } >
469514 < div data-tauri-decorum-tb class = "flex flex-row" />
@@ -615,7 +660,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
615660 "flex items-center min-w-0 justify-end" : true ,
616661 "pr-2" : ! windows ( ) ,
617662 } }
618- data-tauri-drag-region
663+ // data-tauri-drag-region
619664 onMouseDown = { drag }
620665 >
621666 < div id = "opencode-titlebar-right" class = "flex items-center gap-1 shrink-0 justify-end" />
@@ -685,6 +730,7 @@ function TitlebarUpdateIconButton(props: { state: TitlebarUpdatePillState }) {
685730}
686731
687732function TabNavItem ( props : {
733+ ref ?: HTMLDivElement
688734 href : string
689735 server : ServerConnection . Key
690736 directory : string
@@ -694,6 +740,7 @@ function TabNavItem(props: {
694740 onNavigate : ( ) => void
695741 active ?: boolean
696742 activeServer : boolean
743+ forceTruncate ?: boolean
697744} ) {
698745 const closeTab = ( event : MouseEvent ) => {
699746 event . preventDefault ( )
@@ -710,6 +757,7 @@ function TabNavItem(props: {
710757 const [ session ] = createResource (
711758 ( ) => {
712759 const ctx = dirSyncCtx ( )
760+ console . log ( { ctx, sessionId : props . sessionId } )
713761 if ( ! ctx || ! props . sessionId ) return
714762 return [ props . sessionId , ctx ] as const
715763 } ,
@@ -722,6 +770,7 @@ function TabNavItem(props: {
722770
723771 return (
724772 < div
773+ ref = { props . ref }
725774 class = "group relative flex h-7 min-w-24 max-w-60 flex-row items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-[6px] bg-[var(--tab-bg)] px-1.5 [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
726775 data-active = { props . active }
727776 onMouseDown = { ( event ) => {
@@ -731,6 +780,7 @@ function TabNavItem(props: {
731780 >
732781 < Show when = { session . latest } >
733782 { ( session ) => {
783+ console . log ( { session : session ( ) } )
734784 const project = createMemo ( ( ) => projectForSession ( session ( ) , serverCtx ( ) ?. projects . list ( ) ?? [ ] ) )
735785
736786 return (
@@ -756,7 +806,10 @@ function TabNavItem(props: {
756806 } }
757807 </ Show >
758808
759- < div class = "absolute not-group-hover:not-group-data-[active=true]:left-52 group-hover:right-0 group-data-[active=true]:right-0 inset-y-0 flex flex-row items-center pr-1 py-1 w-8 pl-2" >
809+ < div
810+ class = "absolute not-group-hover:not-group-data-[active=true]:not-data-[truncate=true]:left-52 group-hover:right-0 group-data-[active=true]:right-0 data-[truncate=true]:right-0 inset-y-0 flex flex-row items-center pr-1 py-1 w-8 pl-2"
811+ data-truncate = { props . forceTruncate }
812+ >
760813 < div
761814 class = "absolute inset-0 rounded-r-[6px] bg-(image:--inactive-bg) group-hover:bg-(image:--active-bg) group-data-[active=true]:bg-(image:--active-bg)"
762815 style = { {
@@ -796,15 +849,16 @@ function ProjectTabAvatar(props: {
796849 )
797850}
798851
799- function NewSessionTabItem ( props : { href : string ; title : string ; onClose : ( ) => void } ) {
852+ function NewSessionTabItem ( props : { ref ?: HTMLDivElement ; href : string ; title : string ; onClose : ( ) => void } ) {
800853 const closeTab = ( event : MouseEvent ) => {
801854 event . preventDefault ( )
802855 event . stopPropagation ( )
803856 props . onClose ( )
804857 }
805858 return (
806859 < div
807- class = "group relative flex h-7 max-w-60 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--v2-overlay-simple-overlay-pressed)] pl-1.5 pr-8 whitespace-nowrap focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-[var(--v2-border-border-focus)]"
860+ ref = { props . ref }
861+ class = "group relative shrink-0 flex h-7 max-w-60 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--v2-overlay-simple-overlay-pressed)] pl-1.5 pr-8 whitespace-nowrap focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-[var(--v2-border-border-focus)]"
808862 onMouseDown = { ( event ) => {
809863 if ( event . button !== 1 ) return
810864 closeTab ( event )
0 commit comments