Skip to content

Commit ed57bed

Browse files
callmemorgancodex
andauthored
fix(web): skip unchanged shortcut modifier updates - 195x typing latency improvement (#13884)
Co-authored-by: GPT-6 Astra <noreply@openai.com>
1 parent d15210c commit ed57bed

2 files changed

Lines changed: 78 additions & 8 deletions

File tree

‎apps/web/src/shortcutModifierState.test.ts‎

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import { describe, expect, it } from "vite-plus/test";
1+
// @vitest-environment jsdom
2+
3+
import { act, createElement } from "react";
4+
import { createRoot, type Root } from "react-dom/client";
5+
import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
26

37
import {
48
areShortcutModifierStatesEqual,
59
shortcutModifierStateAfterKeyboardEvent,
10+
useShortcutModifierState,
611
type ShortcutModifierState,
712
} from "./shortcutModifierState";
813

@@ -13,6 +18,67 @@ const emptyState = (): ShortcutModifierState => ({
1318
shiftKey: false,
1419
});
1520

21+
describe("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+
1682
function keyboardEventLike(type: "keydown" | "keyup", init: Partial<KeyboardEvent>): KeyboardEvent {
1783
return {
1884
type,

‎apps/web/src/shortcutModifierState.ts‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from "react";
1+
import { useEffect, useRef, useState } from "react";
22

33
export interface ShortcutModifierState {
44
metaKey: boolean;
@@ -28,22 +28,26 @@ export function areShortcutModifierStatesEqual(
2828

2929
export function useShortcutModifierState(): ShortcutModifierState {
3030
const [state, setState] = useState(EMPTY_SHORTCUT_MODIFIER_STATE);
31+
const stateRef = useRef(EMPTY_SHORTCUT_MODIFIER_STATE);
3132

3233
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+
};
3341
const onKeyboardEvent = (event: KeyboardEvent) => {
34-
setState((current) => shortcutModifierStateAfterKeyboardEvent(current, event));
42+
updateState(shortcutModifierStateAfterKeyboardEvent(stateRef.current, event));
3543
};
3644
// Dictation tools (Wispr Flow) paste with a synthetic ⌘V whose Meta keyup
3745
// never reaches the page, so the tracked state stays "⌘ held" forever and
3846
// the thread jump hints stick on screen. A paste is never jump intent, so
3947
// treat it like a blur and reset. A physically held modifier re-registers
4048
// on the next real key event.
4149
const onResetEvent = () => {
42-
setState((current) =>
43-
areShortcutModifierStatesEqual(current, EMPTY_SHORTCUT_MODIFIER_STATE)
44-
? current
45-
: EMPTY_SHORTCUT_MODIFIER_STATE,
46-
);
50+
updateState(EMPTY_SHORTCUT_MODIFIER_STATE);
4751
};
4852

4953
window.addEventListener("keydown", onKeyboardEvent, true);

0 commit comments

Comments
 (0)