Skip to content

Comments

feat(cli): improve CTRL+O experience for both standard and alternate screen buffer (ASB) modes#19010

Merged
jwhelangoog merged 1 commit intogoogle-gemini:mainfrom
jwhelangoog:feature/shell-output-expandability
Feb 21, 2026
Merged

feat(cli): improve CTRL+O experience for both standard and alternate screen buffer (ASB) modes#19010
jwhelangoog merged 1 commit intogoogle-gemini:mainfrom
jwhelangoog:feature/shell-output-expandability

Conversation

@jwhelangoog
Copy link
Contributor

@jwhelangoog jwhelangoog commented Feb 13, 2026

Summary

This PR improves the CTRL+O (Expansion Mode) feature, making it a consistent, flicker-free tool for both standard and alternate screen buffer (ASB) modes. It centralizes layout logic to ensure visual consistency and implements synchronous overflow detection to eliminate UI "pop-in."

Details

Standard Terminal Mode: Turn-Based Expansion

  • Focused Expansion: CTRL+O now specifically toggles the expansion of shell outputs from the current turn (all tool calls since the last user prompt).
  • History Preservation: Older tool outputs in the static history area remain constrained. This prevents "scrollback pollution" by avoiding a full re-print of the chat history.
  • Auto-Reset on Prompt: The expansion state is automatically reset to constrained whenever a new prompt is submitted, ensuring large outputs don't cause unexpected jumps when a new turn begins.
  • Last-Line Truncation Fix: Resolved a bug where the final line of shell output was sometimes omitted in standard mode.

Alternate Screen Buffer (ASB) Mode: Managed Viewport Expansion

  • Fixed ASB Shell Expansion: Resolved the bug where shell results were hard-capped to 15 lines in ASB mode. The calculation now respects the constrainHeight state, allowing for full expansion within the scrollable viewport.
  • Smart Height Recalculation: Centralized utilities (calculateShellMaxLines) now return undefined when unconstrained, enabling components to provide a fluid, full-screen navigation experience.

Technical Polish: Centralized Layout & Flicker Reduction

  • Centralized Layout Utilities: Introduced toolLayoutUtils.ts to unify height and overflow calculations. This ensures that ToolGroupMessage (for overflow detection) and ToolResultDisplay (for actual truncation) stay perfectly in sync.
  • Synchronous Overflow Detection: Moved overflow detection into the render cycle via useMemo. By detecting overflow during render instead of waiting for a ResizeObserver, the expansion hint (ShowMoreLines) appears atomically with the content, eliminating UI flicker.
  • Standardized Notation: Unified keybinding notation to "Ctrl+O" across the UI and documentation.

Related Issues

Fixes #17438

How to Validate

Standard Terminal Mode

  1. Run Gemini CLI in standard mode.
  2. Execute a command with long output (e.g., seq 100).
  3. Press CTRL+O. Verify only the output from the current turn expands.
  4. Verify that historical shell outputs remain constrained.
  5. Submit a new prompt; verify expansion resets to constrained.

Alternate Screen Buffer (ASB) Mode

  1. Run Gemini CLI in ASB mode.
  2. Execute seq 100.
  3. Press CTRL+O. Verify the shell output expands fully and is navigable via internal scrolling.

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jwhelangoog, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the Gemini CLI's standard terminal mode by introducing a feature that allows users to dynamically expand and collapse shell command output using Ctrl+O. This change significantly improves the user experience when dealing with lengthy command outputs by providing an on-demand full view, while maintaining a concise display by default.

Highlights

  • Shell Output Expansion: Implemented Ctrl+O functionality to allow users to expand and collapse active and completed shell process output in standard terminal mode, improving the experience for viewing long outputs.
  • Dynamic Height Constraints: Modified the getShellMaxLines function to return undefined for maximum lines when availableTerminalHeight is undefined in standard terminal mode, enabling full output expansion.
  • UI Re-rendering Mechanism: Added refreshStatic() calls within AppContainer.tsx to ensure the Static history area re-renders correctly when height constraints are toggled via Ctrl+O or automatically reset, specifically for standard terminal mode.
  • Message History Memoization: Updated the message history memoization in MainContent.tsx to depend on uiState.constrainHeight, ensuring that all history items correctly expand or collapse in response to state changes.
Changelog
  • packages/cli/src/ui/AppContainer.tsx
    • Added conditional refreshStatic() calls when toggling constrainHeight to force UI re-renders in standard terminal mode.
  • packages/cli/src/ui/components/MainContent.tsx
    • Modified MemoizedHistoryItemDisplay to conditionally pass undefined for availableTerminalHeight based on uiState.constrainHeight.
    • Included uiState.constrainHeight in the dependency array for message history memoization to ensure proper re-rendering.
  • packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx
    • Updated a test description for clarity regarding alternate buffer behavior when availableTerminalHeight is undefined.
    • Added a new test case to verify that shell output fully expands in standard mode when availableTerminalHeight is undefined.
  • packages/cli/src/ui/components/messages/ShellToolMessage.tsx
    • Adjusted the getShellMaxLines function to return undefined for max lines when availableTerminalHeight is undefined and the application is not in alternate buffer mode, enabling full output expansion.
  • packages/cli/src/ui/components/messages/snapshots/ShellToolMessage.test.tsx.snap
    • Updated a snapshot to reflect the change in a test description.
Activity
  • The author, jwhelangoog, has provided detailed validation steps to thoroughly test the new Ctrl+O functionality in standard terminal mode, covering scenarios for truncated and expanded output, as well as behavior in alternate buffer mode.
  • Tests were added/updated to ensure the new output expansion logic functions as expected.
  • The changes have been validated on MacOS, confirming functionality on a key platform.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a great enhancement to the user experience in standard terminal mode by allowing shell output to be expanded with Ctrl+O. The implementation is solid, with well-reasoned changes across AppContainer, MainContent, and ShellToolMessage. The logic to handle height constraints and trigger re-renders in standard mode via refreshStatic is correctly implemented and conditioned to not affect alternate buffer mode. The updates to memoization dependencies in MainContent ensure the UI reacts correctly to the new state. The new tests in ShellToolMessage.test.tsx effectively validate the expansion behavior. Overall, this is a well-executed feature addition. I have not found any issues of high or critical severity.

@gemini-cli gemini-cli bot added priority/p2 Important but can be addressed in a future release. area/core Issues related to User Interface, OS Support, Core Functionality 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item. labels Feb 13, 2026
@jwhelangoog jwhelangoog marked this pull request as ready for review February 13, 2026 18:16
@jwhelangoog jwhelangoog requested a review from a team as a code owner February 13, 2026 18:16
@jacob314
Copy link
Contributor

I have reviewed the PR and identified a potentially significant design issue related to the standard terminal mode experience.

  1. Re-printing the entire history (refreshStatic): The PR uses refreshStatic() to force the Static component to re-render when Ctrl+O is pressed (or when any key is pressed to collapse). In standard terminal mode, Ink's Static component is designed to append to stdout. Calling refreshStatic() completely replaces the key on the <Static> component, which causes Ink to re-print the entire chat history to the terminal.
  2. Scrollback pollution: If a user has a long conversation and presses Ctrl+O to view an expanded shell output, the entire chat history will be duplicated in their terminal scrollback. Pressing another key to collapse it will duplicate the history again.

Is this acceptable behavior for standard terminal mode? Alternatively, we could consider:

  • Restricting Ctrl+O to only expand active shell processes (which live in pendingItems and do not require refreshStatic()), but leave completed Static items clamped.
  • Suggesting the user run long commands in Alternate Buffer mode instead.

I've also audited the React logic and tests. The waitFor usage is correct, and the snapshots are properly updated. The conditional logic correctly limits this to standard terminal mode.

Let me know how you'd like to proceed with the refreshStatic concern.

@jwhelangoog jwhelangoog changed the title feat(cli): support expanded shell output via Ctrl+O in standard terminal mode feat(cli): improve CTRL+O experience for both standard and alternate screen buffer (ASB) modes Feb 17, 2026
@jwhelangoog
Copy link
Contributor Author

jwhelangoog commented Feb 17, 2026

Screen captures from both modes:

Feb 17: ASB | Standard

Feb 18 Update: ASB | Standard

@jwhelangoog jwhelangoog self-assigned this Feb 19, 2026
@jacob314
Copy link
Contributor

This review was generated by /review-frontend and manually reviewed by Jacob.

Here are a few potential issues and observations, particularly keeping the GEMINI.md React guidelines in mind:

1. Missing Cleanup Function in useEffect (Memory Leak Risk)

In packages/cli/src/ui/AppContainer.tsx, a new useEffect sets a 10-second timeout for showIsExpandableHint using setTimeout(). However, it does not return a cleanup function to clear the timeout if the component unmounts.

  useEffect(() => {
    // ...
    if (hasOverflow) {
      setShowIsExpandableHint(true);
      if (expandHintTimerRef.current) {
        clearTimeout(expandHintTimerRef.current);
      }
      expandHintTimerRef.current = setTimeout(() => {
        setShowIsExpandableHint(false);
      }, 10000); // 10 seconds
    }
    
    // Missing: return () => { if (expandHintTimerRef.current) clearTimeout(expandHintTimerRef.current); }
  }, [overflowState?.overflowingIds.size, isAlternateBuffer, overflowState]);

2. setState inside useEffect (Performance)

The same useEffect in AppContainer.tsx calls setShowIsExpandableHint(true) in response to overflowState changing. React will first render with the new overflowState, run the effect, and then queue another re-render for showIsExpandableHint. This violates the GEMINI.md rule: "IMPORTANT - Don't setState (the 2nd value returned by useState) within a useEffect as that will degrade performance." Consider if there's a way to derive this state or structure it to avoid the double-render.

3. Redundant useEffect Dependencies

The dependency array for the hint useEffect includes both overflowState?.overflowingIds.size and overflowState. Relying on both is redundant.

4. Missing Tests for Timeout Logic

There don't appear to be new tests added in AppContainer.test.tsx verifying the 10-second hint timeout behavior. Consider adding tests utilizing vi.useFakeTimers() to ensure the hint displays and hides accurately.

5. Context Shadowing (OverflowProvider)

In packages/cli/src/ui/components/messages/ToolGroupMessage.tsx, the component conditionally shadows <OverflowProvider> based on isAlternateBuffer. While the comment notes it is to maintain isolation in ASB mode, dynamically shadowing providers in the tree can lead to a fragmented context state that's difficult to trace. Is there a way to handle this isolation without shadowing the provider at this level?

@jwhelangoog
Copy link
Contributor Author

To address the feedback:

  1. Missing Cleanup Function in useEffect (Memory Leak Risk)

    • Resolved in 9f94c2c: Added safe timer cleanup on unmount and ensured existing timers are cleared before new ones are started in AppContainer.tsx.
  2. setState inside useEffect (Performance)

    • Addressed in 9f94c2c: Refactored the hint logic to use a stable boolean dependency (hasOverflowState), eliminating redundant re-renders and infinite loops during active streaming.
  3. Redundant useEffect Dependencies

    • Resolved in 9f94c2c: Simplified the dependency array by derived boolean logic for overflow status.
  4. Missing Tests for Timeout Logic

    • Resolved in 88931a0: Added comprehensive vi.useFakeTimers() tests in AppContainer.test.tsx to verify hint display and timeout behavior.
  5. Context Shadowing (OverflowProvider)

    • Resolved in 5814fe4: Removed redundant OverflowProvider shadowing in ToolGroupMessage.tsx, replacing it with a cleaner prop-based isolation model.

@jacob314 jacob314 force-pushed the feature/shell-output-expandability branch from fb6bba2 to d93cf67 Compare February 20, 2026 23:52
@jacob314 jacob314 force-pushed the feature/shell-output-expandability branch from d93cf67 to 21cf0bd Compare February 21, 2026 00:03
Copy link
Contributor

@jacob314 jacob314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. I'll follow up with code review comments as a PR.

TLDR: main things I will fix in the follow up

  1. flicker of text in alternate buffer mode on changing overflow
  2. fix alternatebufferquittingmessage regression
  3. Consider if we can simply the changes for overflow. definite codesmell that app container is now basically a defacto overflow provider and changes to render.tsx are not ideal either.

@jwhelangoog jwhelangoog added this pull request to the merge queue Feb 21, 2026
Merged via the queue into google-gemini:main with commit 727f9b6 Feb 21, 2026
27 checks passed
@jwhelangoog jwhelangoog deleted the feature/shell-output-expandability branch February 21, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Issues related to User Interface, OS Support, Core Functionality 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item. priority/p2 Important but can be addressed in a future release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants