mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
fix: ESC 关闭 local-jsx 面板后添加 grace-period 防止误触 cancel
/workflows 等面板通过 ESC 关闭时,React unmount 与 chat:cancel keybinding 的 isActive 解除之间存在竞态窗口,导致同一按 ESC 会穿透到 onCancel 并中止正在执行的 Workflow 工具。 添加 500ms grace-period guard:面板关闭时打时间戳,onCancel 在窗口 内吞掉 ESC 并 reset,后续有意 ESC 仍正常取消。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
This commit is contained in:
@@ -1136,6 +1136,18 @@ export function REPL({
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
abortControllerRef.current = abortController;
|
||||
|
||||
// Timestamp (ms) of the most recent local-jsx panel dismissal (e.g. ESC on
|
||||
// /workflows). Used by onCancel's grace-period guard: the ESC that closes
|
||||
// a local-jsx panel (or any quick follow-up ESC within the grace window)
|
||||
// must not fall through to abortController.abort('user-cancel') — otherwise
|
||||
// closing the /workflows panel via ESC would kill the in-flight Workflow
|
||||
// tool. The chat:cancel keybinding's isActive gate (`!isLocalJSXCommand`)
|
||||
// only shields the panel while it's mounted; once React commits the
|
||||
// unmount, the next ESC reaches onCancel unguarded. This ref closes that
|
||||
// race without touching keybinding registration order.
|
||||
const LOCAL_JSX_CLOSE_CANCEL_GRACE_MS = 500;
|
||||
const localJSXClosedAtRef = useRef(0);
|
||||
|
||||
// Track whether the last turn was user-aborted (Ctrl+C / Escape).
|
||||
// When true, useGoalContinuation skips the continuation enqueue so
|
||||
// interrupted turns don't spin into an unstoppable loop. Reset to
|
||||
@@ -1355,6 +1367,9 @@ export function REPL({
|
||||
if (args?.clearLocalJSX) {
|
||||
localJSXCommandRef.current = null;
|
||||
setToolJSXInternal(null);
|
||||
// Stamp the dismissal so onCancel's grace-period guard can swallow
|
||||
// the ESC that just dismissed the panel (and any quick follow-up).
|
||||
localJSXClosedAtRef.current = Date.now();
|
||||
return;
|
||||
}
|
||||
// Otherwise, keep the local JSX command visible - ignore tool updates
|
||||
@@ -2534,6 +2549,24 @@ export function REPL({
|
||||
return;
|
||||
}
|
||||
|
||||
// Grace-period guard: if a local-jsx panel (e.g. /workflows) was just
|
||||
// dismissed via ESC, swallow the same / immediately-following ESC so it
|
||||
// doesn't fall through to abortController.abort('user-cancel') and kill
|
||||
// the in-flight Workflow tool. Single-press ESC closes the panel
|
||||
// (handled by the panel's own useInput → onDone → setToolJSX); the
|
||||
// chat:cancel keybinding's isActive gate shields while the panel is
|
||||
// mounted but not in the React commit window right after unmount.
|
||||
// Reset the stamp so a later, deliberate ESC still cancels normally.
|
||||
if (
|
||||
localJSXClosedAtRef.current !== 0 &&
|
||||
Date.now() - localJSXClosedAtRef.current < LOCAL_JSX_CLOSE_CANCEL_GRACE_MS
|
||||
) {
|
||||
localJSXClosedAtRef.current = 0;
|
||||
logForDebugging('[onCancel] suppressed: local-jsx panel just dismissed');
|
||||
return;
|
||||
}
|
||||
localJSXClosedAtRef.current = 0;
|
||||
|
||||
logForDebugging(`[onCancel] focusedInputDialog=${focusedInputDialog} streamMode=${streamMode}`);
|
||||
|
||||
// Pause proactive mode so the user gets control back.
|
||||
|
||||
Reference in New Issue
Block a user