@@ -170,8 +170,27 @@ export function WmuxApp(props: {
170170 onRestart : ( ) => restartProcess ( activeTabId ) ,
171171 } : null ;
172172
173+ // Build flat navigable sidebar item list
174+ const sidebarItems = useMemo ( ( ) => {
175+ const items : Array < { readonly type : "category" ; readonly name : string } | { readonly type : "tab" ; readonly categoryName : string ; readonly tabId : string } > = [ ] ;
176+ for ( const cat of categories ) {
177+ items . push ( { type : "category" , name : cat . name } ) ;
178+ if ( ! collapsedCategories . has ( cat . name ) && cat . type !== "files" ) {
179+ for ( const tab of cat . tabs ) {
180+ items . push ( { type : "tab" , categoryName : cat . name , tabId : tab . id } ) ;
181+ }
182+ }
183+ }
184+ return items ;
185+ } , [ categories , collapsedCategories ] ) ;
186+
173187 // Keyboard shortcuts
174188 useEffect ( ( ) => {
189+ const isTerminalFocused = ( ) : boolean => {
190+ const el = document . activeElement ;
191+ return el != null && el . closest ( ".xterm" ) != null ;
192+ } ;
193+
175194 const handler = ( e : KeyboardEvent ) : void => {
176195 const mod = e . metaKey || e . ctrlKey ;
177196 if ( mod && e . key === "k" ) { e . preventDefault ( ) ; setCmdkOpen ( ( p ) => ! p ) ; return ; }
@@ -188,11 +207,33 @@ export function WmuxApp(props: {
188207 const idx = orderedTabs . findIndex ( ( t ) => t . id === activeTabId ) ;
189208 const next = e . key === "]" ? ( idx + 1 ) % orderedTabs . length : ( idx - 1 + orderedTabs . length ) % orderedTabs . length ;
190209 selectTab ( orderedTabs [ next ] ! . id ) ;
210+ return ;
211+ }
212+
213+ // Arrow key sidebar navigation (only when terminal is not focused)
214+ if ( ( e . key === "ArrowUp" || e . key === "ArrowDown" ) && ! isTerminalFocused ( ) && ! cmdkOpen && sidebarItems . length > 0 ) {
215+ e . preventDefault ( ) ;
216+ const currentIdx = sidebarItems . findIndex ( ( item ) =>
217+ item . type === "tab"
218+ ? item . tabId === activeTabId && item . categoryName === activeCategory
219+ : item . name === activeCategory && ! sidebarItems . some ( ( s ) => s . type === "tab" && s . categoryName === activeCategory && s . tabId === activeTabId ) ,
220+ ) ;
221+ const delta = e . key === "ArrowDown" ? 1 : - 1 ;
222+ const nextIdx = currentIdx === - 1
223+ ? 0
224+ : ( currentIdx + delta + sidebarItems . length ) % sidebarItems . length ;
225+ const next = sidebarItems [ nextIdx ] ! ;
226+ if ( next . type === "category" ) {
227+ selectCategory ( next . name ) ;
228+ } else {
229+ selectCategory ( next . categoryName ) ;
230+ selectTab ( next . tabId ) ;
231+ }
191232 }
192233 } ;
193234 window . addEventListener ( "keydown" , handler ) ;
194235 return ( ) => window . removeEventListener ( "keydown" , handler ) ;
195- } , [ cmdkOpen , categories , selectCategory , orderedTabs , activeTabId , selectTab ] ) ;
236+ } , [ cmdkOpen , categories , selectCategory , orderedTabs , activeTabId , selectTab , sidebarItems , activeCategory ] ) ;
196237
197238 const toggleCollapse = useCallback ( ( name : string ) => {
198239 setCollapsedCategories ( ( prev ) => { const next = new Set ( prev ) ; next . has ( name ) ? next . delete ( name ) : next . add ( name ) ; return next ; } ) ;
0 commit comments