From afed73fa5a471cb913918578337e9057a8699347 Mon Sep 17 00:00:00 2001 From: KRATOS <84986124+gnanam1990@users.noreply.github.com> Date: Fri, 3 Apr 2026 23:04:41 +0530 Subject: [PATCH] fix: resolve keyboard input freeze on Windows and Mac at startup (#285) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three compounding issues caused keyboard input to appear frozen or drop characters on startup, particularly on Windows CMD/PowerShell and Mac terminal environments. Issue 1 — earlyInput disabled (cli.tsx): The rebrand commit gated startCapturingEarlyInput() behind an opt-in env flag (OPENCLAUDE_ENABLE_EARLY_INPUT=1), which meant any characters typed before React finished mounting were silently dropped. Users who type immediately after launch saw an empty input box with no indication their keystrokes were lost. Flipped to an opt-out flag (OPENCLAUDE_DISABLE_EARLY_INPUT=1) so early input capture is on by default, matching the original upstream behaviour. Issue 2 — stdin.resume() called before listener attached (App.tsx): stdin.resume() put the stream into flowing mode before the data/readable listener was registered. Any input arriving in that gap was queued and delivered in a burst when the listener connected, which could flood React's scheduler and stall input processing. Moved resume() to after the listener is attached so the stream only flows once the handler is ready. Issue 3 — AnimatedAsterisk fires ~60 React re-renders in 3s (AnimatedAsterisk.tsx): The startup screen colour sweep animation runs at 50ms intervals for 3000ms total. Each tick triggers a full re-render of the startup screen subtree, which competes with stdin event processing in React's microtask queue. On Windows, where the event loop scheduler is slower, this reliably caused typing to lag or freeze for the first few seconds after launch. The animation is now skipped on Windows (process.platform === 'win32'), showing the icon in its settled state immediately. Mac and Linux are unaffected. Closes #228, #220, #205 --- src/components/LogoV2/AnimatedAsterisk.tsx | 4 +++- src/entrypoints/cli.tsx | 2 +- src/ink/components/App.tsx | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/LogoV2/AnimatedAsterisk.tsx b/src/components/LogoV2/AnimatedAsterisk.tsx index 116f9083..7fb9c8c0 100644 --- a/src/components/LogoV2/AnimatedAsterisk.tsx +++ b/src/components/LogoV2/AnimatedAsterisk.tsx @@ -20,7 +20,9 @@ export function AnimatedAsterisk({ // Read prefersReducedMotion once at mount — no useSettings() subscription, // since that would re-render whenever settings change. const [reducedMotion] = useState(() => getInitialSettings().prefersReducedMotion ?? false); - const [done, setDone] = useState(reducedMotion); + // Skip animation on Windows — 60 re-renders in 3s competes with stdin processing + // and causes keyboard freeze on CMD/PowerShell (issues #228, #205). + const [done, setDone] = useState(reducedMotion || process.platform === 'win32'); // useAnimationFrame's clock is shared — capture our start offset so the // sweep always begins at hue 0 regardless of when we mount. const startTimeRef = useRef(null); diff --git a/src/entrypoints/cli.tsx b/src/entrypoints/cli.tsx index c82a0bb2..39614f19 100644 --- a/src/entrypoints/cli.tsx +++ b/src/entrypoints/cli.tsx @@ -416,7 +416,7 @@ async function main(): Promise { } // No special flags detected, load and run the full CLI - if (process.env.OPENCLAUDE_ENABLE_EARLY_INPUT === '1') { + if (process.env.OPENCLAUDE_DISABLE_EARLY_INPUT !== '1') { const { startCapturingEarlyInput } = await import('../utils/earlyInput.js'); diff --git a/src/ink/components/App.tsx b/src/ink/components/App.tsx index 7651423a..c75dec7e 100644 --- a/src/ink/components/App.tsx +++ b/src/ink/components/App.tsx @@ -229,12 +229,12 @@ export default class App extends PureComponent { stopCapturingEarlyInput(); stdin.ref(); stdin.setRawMode(true); - stdin.resume(); if (this.stdinMode === 'data') { stdin.addListener('data', this.handleDataChunk); } else { stdin.addListener('readable', this.handleReadable); } + stdin.resume(); // Enable bracketed paste mode this.props.stdout.write(EBP); // Enable terminal focus reporting (DECSET 1004)