@@ -488,6 +488,111 @@ describe("control UI routing", () => {
488488 expect ( app . chatMobileControlsOpen ) . toBe ( false ) ;
489489 } ) ;
490490
491+ it ( "dispatches guarded dashboard chat shortcuts" , async ( ) => {
492+ const app = mountApp ( "/overview" ) ;
493+ await app . updateComplete ;
494+
495+ const scrollToBottom = vi . spyOn ( app , "scrollToBottom" ) ;
496+ app . chatNewMessagesBelow = true ;
497+ const overviewJump = new KeyboardEvent ( "keydown" , {
498+ key : "N" ,
499+ bubbles : true ,
500+ cancelable : true ,
501+ } ) ;
502+ document . dispatchEvent ( overviewJump ) ;
503+
504+ expect ( overviewJump . defaultPrevented ) . toBe ( false ) ;
505+ expect ( scrollToBottom ) . not . toHaveBeenCalled ( ) ;
506+
507+ const slash = new KeyboardEvent ( "keydown" , {
508+ key : "/" ,
509+ bubbles : true ,
510+ cancelable : true ,
511+ } ) ;
512+ document . dispatchEvent ( slash ) ;
513+ await app . updateComplete ;
514+ await nextFrame ( ) ;
515+
516+ const composer = expectElement (
517+ app ,
518+ ".agent-chat__composer-combobox textarea" ,
519+ HTMLTextAreaElement ,
520+ ) ;
521+ expect ( app . tab ) . toBe ( "chat" ) ;
522+ expect ( slash . defaultPrevented ) . toBe ( true ) ;
523+ expect ( document . activeElement ) . toBe ( composer ) ;
524+
525+ app . chatNewMessagesBelow = true ;
526+ composer . blur ( ) ;
527+ app . requestUpdate ( ) ;
528+ await app . updateComplete ;
529+
530+ const jump = new KeyboardEvent ( "keydown" , {
531+ key : "N" ,
532+ bubbles : true ,
533+ cancelable : true ,
534+ } ) ;
535+ document . dispatchEvent ( jump ) ;
536+
537+ expect ( jump . defaultPrevented ) . toBe ( true ) ;
538+ expect ( scrollToBottom ) . toHaveBeenCalledOnce ( ) ;
539+
540+ const editableJump = new KeyboardEvent ( "keydown" , {
541+ key : "N" ,
542+ bubbles : true ,
543+ cancelable : true ,
544+ } ) ;
545+ composer . dispatchEvent ( editableJump ) ;
546+
547+ expect ( editableJump . defaultPrevented ) . toBe ( false ) ;
548+ expect ( scrollToBottom ) . toHaveBeenCalledOnce ( ) ;
549+
550+ app . paletteOpen = true ;
551+ const escape = new KeyboardEvent ( "keydown" , {
552+ key : "Escape" ,
553+ bubbles : true ,
554+ cancelable : true ,
555+ } ) ;
556+ document . dispatchEvent ( escape ) ;
557+
558+ expect ( escape . defaultPrevented ) . toBe ( true ) ;
559+ expect ( app . paletteOpen ) . toBe ( false ) ;
560+
561+ app . chatMobileControlsOpen = true ;
562+ composer . focus ( ) ;
563+ const editableEscape = new KeyboardEvent ( "keydown" , {
564+ key : "Escape" ,
565+ bubbles : true ,
566+ cancelable : true ,
567+ } ) ;
568+ composer . dispatchEvent ( editableEscape ) ;
569+
570+ expect ( editableEscape . defaultPrevented ) . toBe ( false ) ;
571+ expect ( app . chatMobileControlsOpen ) . toBe ( true ) ;
572+
573+ const plaintextOnly = document . createElement ( "div" ) ;
574+ plaintextOnly . setAttribute ( "contenteditable" , "plaintext-only" ) ;
575+ plaintextOnly . tabIndex = 0 ;
576+ app . append ( plaintextOnly ) ;
577+ plaintextOnly . focus ( ) ;
578+ app . paletteOpen = true ;
579+ app . chatMobileControlsOpen = true ;
580+
581+ for ( const key of [ "/" , "N" , "Escape" ] ) {
582+ const event = new KeyboardEvent ( "keydown" , {
583+ key,
584+ bubbles : true ,
585+ cancelable : true ,
586+ } ) ;
587+ plaintextOnly . dispatchEvent ( event ) ;
588+ expect ( event . defaultPrevented , key ) . toBe ( false ) ;
589+ }
590+
591+ expect ( app . paletteOpen ) . toBe ( true ) ;
592+ expect ( app . chatMobileControlsOpen ) . toBe ( true ) ;
593+ expect ( scrollToBottom ) . toHaveBeenCalledOnce ( ) ;
594+ } ) ;
595+
491596 it ( "preserves session navigation and keeps focus mode scoped to chat" , async ( ) => {
492597 const app = mountApp ( "/sessions?session=agent:main:subagent:task-123" ) ;
493598 await app . updateComplete ;
0 commit comments