@@ -33,7 +33,12 @@ export function useShortcutModifierState(): ShortcutModifierState {
3333 const onKeyboardEvent = ( event : KeyboardEvent ) => {
3434 setState ( ( current ) => shortcutModifierStateAfterKeyboardEvent ( current , event ) ) ;
3535 } ;
36- const onWindowBlur = ( ) => {
36+ // Dictation tools (Wispr Flow) paste with a synthetic ⌘V whose Meta keyup
37+ // never reaches the page, so the tracked state stays "⌘ held" forever and
38+ // the thread jump hints stick on screen. A paste is never jump intent, so
39+ // treat it like a blur and reset. A physically held modifier re-registers
40+ // on the next real key event.
41+ const onResetEvent = ( ) => {
3742 setState ( ( current ) =>
3843 areShortcutModifierStatesEqual ( current , EMPTY_SHORTCUT_MODIFIER_STATE )
3944 ? current
@@ -43,11 +48,13 @@ export function useShortcutModifierState(): ShortcutModifierState {
4348
4449 window . addEventListener ( "keydown" , onKeyboardEvent , true ) ;
4550 window . addEventListener ( "keyup" , onKeyboardEvent , true ) ;
46- window . addEventListener ( "blur" , onWindowBlur ) ;
51+ window . addEventListener ( "paste" , onResetEvent , true ) ;
52+ window . addEventListener ( "blur" , onResetEvent ) ;
4753 return ( ) => {
4854 window . removeEventListener ( "keydown" , onKeyboardEvent , true ) ;
4955 window . removeEventListener ( "keyup" , onKeyboardEvent , true ) ;
50- window . removeEventListener ( "blur" , onWindowBlur ) ;
56+ window . removeEventListener ( "paste" , onResetEvent , true ) ;
57+ window . removeEventListener ( "blur" , onResetEvent ) ;
5158 } ;
5259 } , [ ] ) ;
5360
@@ -84,11 +91,17 @@ export function shortcutModifierStateAfterKeyboardEvent(
8491 [ normalizedModifierKey ] : event . type === "keydown" ,
8592 } ;
8693 } else {
94+ // Flags on non-modifier keys may only clear a bit, never set one. After a
95+ // dictation tool's synthetic ⌘V (Wispr Flow), the browser can keep
96+ // reporting metaKey=true on real key events (Enter to submit) until the
97+ // user physically taps ⌘. Trusting that flag would mark ⌘ as held and
98+ // stick the thread jump hints. Setting a bit requires a real modifier
99+ // keydown, handled above.
87100 nextState = {
88- metaKey : event . metaKey ,
89- ctrlKey : event . ctrlKey ,
90- altKey : event . altKey ,
91- shiftKey : event . shiftKey ,
101+ metaKey : currentState . metaKey && event . metaKey ,
102+ ctrlKey : currentState . ctrlKey && event . ctrlKey ,
103+ altKey : currentState . altKey && event . altKey ,
104+ shiftKey : currentState . shiftKey && event . shiftKey ,
92105 } ;
93106 }
94107
0 commit comments