authorgravatar for morgan@skyslope.comMorgana Allen <morgan@skyslope.com> 2026-09-28 11:10:54-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-28 18:10:54+00:00
loged57bed8e71b2e89a9350c94bda0dacc71cd4817
tree33daa79de5ce784122142172ac1d56d35a08d6fe
parentd15210cd3da79f9a1a495a6309d912d76362a046
signature Signed by PGP key B5690EEEBB952194

fix(web): skip unchanged shortcut modifier updates - 195x typing latency improvement (#13884)

Co-authored-by: GPT-6 Astra <noreply@openai.com>

2 files changed, 78 insertions(+), 8 deletions(-)

apps/web/src/shortcutModifierState.test.ts+67-1
...@@ -1,8 +1,13 @@...@@ -1,8 +1,13 @@
1import { describe, expect, it } from "vite-plus/test";1// @vitest-environment jsdom
2
3import { act, createElement } from "react";
4import { createRoot, type Root } from "react-dom/client";
5import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
26
3import {7import {
4 areShortcutModifierStatesEqual,8 areShortcutModifierStatesEqual,
5 shortcutModifierStateAfterKeyboardEvent,9 shortcutModifierStateAfterKeyboardEvent,
10 useShortcutModifierState,
6 type ShortcutModifierState,11 type ShortcutModifierState,
7} from "./shortcutModifierState";12} from "./shortcutModifierState";
813
...@@ -13,6 +18,67 @@ const emptyState = (): ShortcutModifierState => ({...@@ -13,6 +18,67 @@ const emptyState = (): ShortcutModifierState => ({
13 shiftKey: false,18 shiftKey: false,
14});19});
1520
21describe("useShortcutModifierState", () => {
22 let root: Root;
23 let container: HTMLDivElement;
24
25 beforeEach(() => {
26 vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
27 container = document.createElement("div");
28 document.body.append(container);
29 root = createRoot(container);
30 });
31
32 afterEach(async () => {
33 await act(async () => root.unmount());
34 container.remove();
35 vi.unstubAllGlobals();
36 });
37
38 it.each(["keyup", "paste", "blur"] as const)(
39 "does not render for unchanged modifiers after %s resets the state",
40 async (reset) => {
41 const render = vi.fn();
42 function Consumer() {
43 render(useShortcutModifierState());
44 return null;
45 }
46 await act(async () => {
47 root.render(createElement(Consumer));
48 });
49 const press = async (type: "keydown" | "keyup", key: string, shiftKey = false) => {
50 await act(async () => {
51 window.dispatchEvent(new KeyboardEvent(type, { key, shiftKey }));
52 });
53 };
54
55 await press("keydown", "Shift", true);
56 expect(render).toHaveBeenLastCalledWith({ ...emptyState(), shiftKey: true });
57 if (reset === "keyup") {
58 await press("keyup", "Shift");
59 } else {
60 await act(async () => {
61 window.dispatchEvent(new Event(reset));
62 });
63 }
64 expect(render).toHaveBeenLastCalledWith(emptyState());
65 render.mockClear();
66
67 for (const key of "typing") {
68 await press("keydown", key);
69 await press("keyup", key);
70 }
71 await act(async () => {
72 window.dispatchEvent(new Event("paste"));
73 window.dispatchEvent(new Event("blur"));
74 });
75 expect(render).not.toHaveBeenCalled();
76 await press("keydown", "Shift", true);
77 expect(render).toHaveBeenCalledExactlyOnceWith({ ...emptyState(), shiftKey: true });
78 },
79 );
80});
81
16function keyboardEventLike(type: "keydown" | "keyup", init: Partial<KeyboardEvent>): KeyboardEvent {82function keyboardEventLike(type: "keydown" | "keyup", init: Partial<KeyboardEvent>): KeyboardEvent {
17 return {83 return {
18 type,84 type,
apps/web/src/shortcutModifierState.ts+11-7
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import { useEffect, useState } from "react";1import { useEffect, useRef, useState } from "react";
22
3export interface ShortcutModifierState {3export interface ShortcutModifierState {
4 metaKey: boolean;4 metaKey: boolean;
...@@ -28,10 +28,18 @@ export function areShortcutModifierStatesEqual(...@@ -28,10 +28,18 @@ export function areShortcutModifierStatesEqual(
2828
29export function useShortcutModifierState(): ShortcutModifierState {29export function useShortcutModifierState(): ShortcutModifierState {
30 const [state, setState] = useState(EMPTY_SHORTCUT_MODIFIER_STATE);30 const [state, setState] = useState(EMPTY_SHORTCUT_MODIFIER_STATE);
31 const stateRef = useRef(EMPTY_SHORTCUT_MODIFIER_STATE);
3132
32 useEffect(() => {33 useEffect(() => {
34 const updateState = (next: ShortcutModifierState) => {
35 // Even a no-op state dispatch can cost work in the sidebar's large tree.
36 // Ordinary typing must return before dispatching a React update.
37 if (areShortcutModifierStatesEqual(stateRef.current, next)) return;
38 stateRef.current = next;
39 setState(next);
40 };
33 const onKeyboardEvent = (event: KeyboardEvent) => {41 const onKeyboardEvent = (event: KeyboardEvent) => {
34 setState((current) => shortcutModifierStateAfterKeyboardEvent(current, event));42 updateState(shortcutModifierStateAfterKeyboardEvent(stateRef.current, event));
35 };43 };
36 // Dictation tools (Wispr Flow) paste with a synthetic ⌘V whose Meta keyup44 // Dictation tools (Wispr Flow) paste with a synthetic ⌘V whose Meta keyup
37 // never reaches the page, so the tracked state stays "⌘ held" forever and45 // never reaches the page, so the tracked state stays "⌘ held" forever and
...@@ -39,11 +47,7 @@ export function useShortcutModifierState(): ShortcutModifierState {...@@ -39,11 +47,7 @@ export function useShortcutModifierState(): ShortcutModifierState {
39 // treat it like a blur and reset. A physically held modifier re-registers47 // treat it like a blur and reset. A physically held modifier re-registers
40 // on the next real key event.48 // on the next real key event.
41 const onResetEvent = () => {49 const onResetEvent = () => {
42 setState((current) =>50 updateState(EMPTY_SHORTCUT_MODIFIER_STATE);
43 areShortcutModifierStatesEqual(current, EMPTY_SHORTCUT_MODIFIER_STATE)
44 ? current
45 : EMPTY_SHORTCUT_MODIFIER_STATE,
46 );
47 };51 };
4852
49 window.addEventListener("keydown", onKeyboardEvent, true);53 window.addEventListener("keydown", onKeyboardEvent, true);