fix(cli): remove residual blank lines after MCP init completes (#3509)

* fix(cli): remove residual blank lines after MCP init completes (#3095)

ConfigInitDisplay rendered <Box marginTop={1}> plus a content line, so
the live area grew by 2 rows during startup. When initialization
finished and the component unmounted, Ink shrank the live area but the
rows it had already committed to the terminal scrollback cannot be
reclaimed, leaving a visible gap above the input.

Move the MCP init status into the Footer's left-bottom status slot
(always mounted, fixed height) so the live area height stays constant
across the init → ready transition. The status participates in the
existing priority chain: ctrlC / ctrlD / escape / vim / shell /
autoAccept / configInit / hint.

* fix(cli): suppress MCP init message when custom status line is active

Audit follow-up. Previously the configInit branch preceded the
suppressHint branch in the footer's left-bottom priority chain. With
a custom status line configured, <Text>{null}</Text> collapses to
zero rows in Ink, so the footer's bottom row went from 1 row during
init to 0 rows after — a 1-row height oscillation that reintroduces
the same scrollback-residue symptom the original fix eliminated in
the default case.

Swap the order so suppressHint short-circuits to null first: the
init message now shares the hint's suppression rule, keeping the
footer's height constant in every configuration.

Also:
- Gate the hook's return on isConfigInitialized directly instead of
  letting the effect clear state, avoiding a one-frame flash where
  the stale "Initializing..." message leaks through on the first
  render after init completes.
- Cover the new behavior with three Footer tests, including a
  regression test for the custom-status-line case.

* fix(cli): show MCP init progress even under a custom status line

Reverting a UX trade-off introduced in the previous commit. That
change suppressed the init message whenever a custom status line was
active, arguing that <Text>{null}</Text> collapses to zero rows in
Ink and any non-zero init row would re-create a one-row shrink on
completion.

Zero shrink was the wrong goal. Hiding init progress from users who
have configured a status line is a real usability loss — the status
line does not surface MCP connection state, so those users now see
no feedback during startup. A one-time, one-line shrink on init
completion is a far smaller regression than the original two-row
scrollback residue this PR was created to fix, and strictly better
than the silent alternative.

Keep the init message in the left-bottom slot and let it sit above
suppressHint in the priority chain. Update the regression test so
that it pins the new behavior (init is visible with or without a
status line) and prevents the suppression from being reintroduced.

* fix(cli): keep MCP init progress visible in screen-reader mode

Footer is gated behind !isScreenReaderEnabled, so moving the init
message inside Footer silenced it for screen-reader users. Render the
same message as a plain Text node in Composer when the screen reader is
active — screen-reader users don't suffer from the live-area residual
row issue that motivated the original move, so an independent node is
safe for them.

* refactor(cli): drop duplicated screen-reader init path and show progress under YOLO

- ScreenReaderAppLayout already mounts <Footer /> directly, so the
  separate <Text> branch in Composer was producing a duplicated
  'Connecting to MCP servers...' line in screen-reader mode. Remove it.
- Move configInitMessage ahead of AutoAcceptIndicator in the footer's
  priority chain so users launched with YOLO / auto-accept-edits still
  see the ~1s startup progress; the approval-mode indicator takes over
  as soon as init finishes.
- Add unit tests for useConfigInitMessage covering the idle, progress,
  reset, and unsubscribe paths.
This commit is contained in:
Edenman 2026-04-24 09:50:20 +08:00 committed by GitHub
parent 4e0a37549d
commit 3182500835
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 183 additions and 21 deletions

View file

@ -16,7 +16,6 @@ import { useUIActions } from '../contexts/UIActionsContext.js';
import { useVimMode } from '../contexts/VimModeContext.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { StreamingState, type HistoryItemToolGroup } from '../types.js';
import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
import { FeedbackDialog } from '../FeedbackDialog.js';
import { t } from '../../i18n/index.js';
@ -104,8 +103,6 @@ export const Composer = () => {
/>
)}
{!uiState.isConfigInitialized && <ConfigInitDisplay />}
<QueuedMessageDisplay messageQueue={uiState.messageQueue} />
{uiState.isFeedbackDialogOpen && <FeedbackDialog />}