From a626497f731392ae686a5b5390019c228cf5c512 Mon Sep 17 00:00:00 2001
From: tt-a1i <53142663+tt-a1i@users.noreply.github.com>
Date: Sat, 20 Jun 2026 01:13:28 +0800
Subject: [PATCH] fix(cli): keep keypress handlers current
---
.../messages/AskUserQuestionDialog.test.tsx | 51 ++++++++++++++++---
packages/cli/src/ui/hooks/useKeypress.ts | 15 ++++--
2 files changed, 55 insertions(+), 11 deletions(-)
diff --git a/packages/cli/src/ui/components/messages/AskUserQuestionDialog.test.tsx b/packages/cli/src/ui/components/messages/AskUserQuestionDialog.test.tsx
index bdb7d652454..cf5e119be74 100644
--- a/packages/cli/src/ui/components/messages/AskUserQuestionDialog.test.tsx
+++ b/packages/cli/src/ui/components/messages/AskUserQuestionDialog.test.tsx
@@ -13,6 +13,29 @@ import stripAnsi from 'strip-ansi';
const wait = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
const clean = (value: string | undefined) => stripAnsi(value ?? '');
+const waitForFrame = async (
+ predicate: () => void,
+ options: { timeout?: number; interval?: number } = {},
+) => {
+ const { timeout = 1000, interval = 10 } = options;
+ const start = Date.now();
+ let lastError: unknown;
+
+ while (Date.now() - start < timeout) {
+ try {
+ predicate();
+ return;
+ } catch (error) {
+ lastError = error;
+ }
+ await wait(interval);
+ }
+
+ if (lastError) {
+ throw lastError;
+ }
+ throw new Error('waitForFrame timed out');
+};
const createSingleQuestion = (
overrides: Partial<
@@ -301,22 +324,36 @@ describe('', () => {
await wait();
stdin.write('4'); // Select "Other" custom input
- await wait(150);
- expect(clean(lastFrame())).toContain('❯ 4.');
+ await waitForFrame(() => {
+ expect(clean(lastFrame())).toContain('❯ 4.');
+ });
+ await wait();
stdin.write('j');
- await wait(150);
+ await waitForFrame(() => {
+ const frame = clean(lastFrame());
+ expect(frame).toContain('❯ 4.');
+ expect(frame).toContain('j');
+ });
+
stdin.write('k');
- await wait(150);
- expect(clean(lastFrame())).toContain('❯ 4.');
+ await waitForFrame(() => {
+ const frame = clean(lastFrame());
+ expect(frame).toContain('❯ 4.');
+ expect(frame).toContain('jk');
+ });
stdin.write('\u0010'); // Ctrl+P
await wait();
- expect(clean(lastFrame())).toContain('❯ 3. Green');
+ await waitForFrame(() => {
+ expect(clean(lastFrame())).toContain('❯ 3. Green');
+ });
stdin.write('\u000E'); // Ctrl+N
await wait();
- expect(clean(lastFrame())).toContain('❯ 4.');
+ await waitForFrame(() => {
+ expect(clean(lastFrame())).toContain('❯ 4.');
+ });
unmount();
});
diff --git a/packages/cli/src/ui/hooks/useKeypress.ts b/packages/cli/src/ui/hooks/useKeypress.ts
index 1ff3ae2778d..2ed1bc550ed 100644
--- a/packages/cli/src/ui/hooks/useKeypress.ts
+++ b/packages/cli/src/ui/hooks/useKeypress.ts
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { useEffect } from 'react';
+import { useCallback, useEffect, useRef } from 'react';
import type { KeypressHandler, Key } from '../contexts/KeypressContext.js';
import { useKeypressContext } from '../contexts/KeypressContext.js';
@@ -22,15 +22,22 @@ export function useKeypress(
{ isActive }: { isActive: boolean },
) {
const { subscribe, unsubscribe } = useKeypressContext();
+ const onKeypressRef = useRef(onKeypress);
+
+ onKeypressRef.current = onKeypress;
+
+ const handleKeypress = useCallback((key) => {
+ onKeypressRef.current(key);
+ }, []);
useEffect(() => {
if (!isActive) {
return;
}
- subscribe(onKeypress);
+ subscribe(handleKeypress);
return () => {
- unsubscribe(onKeypress);
+ unsubscribe(handleKeypress);
};
- }, [isActive, onKeypress, subscribe, unsubscribe]);
+ }, [isActive, handleKeypress, subscribe, unsubscribe]);
}