Commit Graph

61 Commits

  • feat(tui): remove Zellij TUI workarounds (#22214)
    ## Why
    
    We added Zellij-specific TUI workarounds because older Zellij behavior
    did not work with Codex's normal terminal model:
    
    - #8555 made `tui.alternate_screen = "auto"` disable alternate screen in
    Zellij so transcript history stayed available.
    - #16578 avoided scroll-region operations in Zellij by emitting raw
    newlines and using a separate composer styling path.
    
    This PR removes both workarounds because the latest Zellij release
    tested locally (`zellij 0.44.1`) works correctly with Codex's standard
    TUI behavior: normal alternate-screen handling, redraw, and history
    insertion.
    
    ## What Changed
    
    - Removed the `InsertHistoryMode::Zellij` path and the Zellij-only
    newline scrollback insertion behavior.
    - Removed cached `is_zellij` state from the TUI and composer.
    - Removed Zellij-specific composer styling, the helper snapshot, and the
    `TerminalInfo::is_zellij()` convenience method that only served this
    workaround.
    - Changed `tui.alternate_screen = "auto"` to use alternate screen for
    Zellij too; `--no-alt-screen` and `tui.alternate_screen = "never"` still
    preserve the inline mode escape hatch.
    - Updated the generated config schema description for
    `tui.alternate_screen`.
    
    ## How to Test
    
    Manual smoke path used with `zellij 0.44.1`:
    
    1. Build and run this branch inside a Zellij `0.44.1` session with
    default config.
    2. Start Codex normally and produce enough assistant/tool output to
    create scrollback.
    3. Confirm the transcript remains readable, the composer renders
    normally, and scrolling through terminal history works.
    4. Resize the Zellij pane while output exists and confirm the TUI
    redraws without duplicated, missing, or stale rows.
    5. Compare with `--no-alt-screen` or `-c tui.alternate_screen=never` if
    you want to verify the inline fallback still works.
    
    Targeted tests:
    - `just write-config-schema`
    - `just fmt`
    - `just fix -p codex-tui`
    - `cargo test -p codex-terminal-detection`
    - `cargo test -p codex-tui alternate_screen_auto_uses_alt_screen`
    
    Attempted but did not complete locally:
    - `cargo test -p codex-tui` built and ran the new test successfully,
    then failed later on unrelated local failures in
    `status_permissions_full_disk_managed_*` and a stack overflow in
    `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`.
    
    ## Documentation
    
    No developers.openai.com Codex documentation update is needed for this
    revert.
  • feat(tui): add ambient terminal pets (#21206)
    ## Why
    
    The Codex App has animated pets, but the TUI had no equivalent ambient
    companion surface. This brings that experience into terminal Codex while
    keeping the main chat flow usable: the pet should feel present, but it
    cannot cover transcript text, composer input, approvals, or picker
    content.
    
    The feature also needs to be terminal-aware. Different terminals support
    different image protocols, tmux can interfere with image rendering, and
    some users will want pets disabled entirely or anchored differently
    depending on their layout.
    
    <table>
    <tr><td>
    <img width="4110" height="2584" alt="CleanShot 2026-05-05 at 12 41
    45@2x"
    src="https://github.com/user-attachments/assets/68a1fcbc-2104-48d6-b834-69c6aaa95cdf"
    />
    <p align="center">macOS - Ghostty, iTerm2 and WezTerm with Custom
    Pet</p>
    </td></tr>
    <tr><td>
    ![Uploading CleanShot 2026-05-10 at 20.28.30.png…]()
    <p align="center">Windows Terminal</p>
    </td></tr>
    <tr><td>
    <img width="3902" height="2752" alt="CleanShot 2026-05-05 at 12 39
    02@2x"
    src="https://github.com/user-attachments/assets/300e2931-6b00-467e-91cb-ab8e28470500"
    />
    <p align="center">Linux - WezTerm and Ghostty</p>
    </td></tr>
    </table>
    
    ## What Changed
    
    - Add a TUI ambient pet renderer in `codex-rs/tui/src/pets/`.
    - Port the app-style pet animation states so the sprite changes with
    task status, waiting-for-input states, review/ready states, and
    failures.
    - Add `/pets` selection UI with a preview pane, loading state, built-in
    pet choices, and a first-row `Disable terminal pets` option.
    - Download built-in pet spritesheets on demand from the same public CDN
    path already used by Android, under
    `https://persistent.oaistatic.com/codex/pets/v1/...`, and cache them
    locally under `~/.codex/cache/tui-pets/`.
    - Keep custom pets local.
    - Add config support for pet selection, disabling pets, and choosing
    whether the pet follows the composer bottom or anchors to the terminal
    bottom.
    - Reserve layout space around the pet so transcript wrapping, live
    responses, and composer input do not render underneath the sprite.
    - Gate image rendering by terminal capability, disable image pets under
    tmux, and support both Kitty Graphics and SIXEL terminals.
    - Add redraw cleanup for terminal image artifacts, including sixel cell
    clearing.
    
    ## Current Scope
    
    - This is an initial TUI version of ambient pets, not full App parity.
    - It focuses on ambient sprite rendering, `/pets` selection, custom
    pets, terminal capability gating, and on-demand CDN-backed built-in
    assets.
    - The ambient text overlay is currently disabled, so the TUI renders the
    pet sprite without extra status text beside it.
    
    ## How to Test
    
    1. Start Codex TUI in a terminal with image support.
    2. Run `/pets`.
    3. Confirm the picker shows built-in pets plus custom pets, and the
    first item is `Disable terminal pets`.
    4. On a fresh `~/.codex/cache/tui-pets/`, move onto a built-in pet and
    confirm the first preview downloads the spritesheet from the shared
    Codex pets CDN and renders successfully.
    5. Move through the pet list and confirm subsequent built-in previews
    use the local cache.
    6. Select a pet, then send and receive messages. Confirm transcript and
    composer text wrap before the pet instead of rendering underneath the
    sprite.
    7. Change the pet anchor setting and confirm the pet can either follow
    the composer bottom or sit at the terminal bottom.
    8. Return to `/pets`, choose `Disable terminal pets`, and confirm the
    sprite disappears cleanly.
    
    Targeted tests:
    - `cargo test -p codex-tui ambient_pet_`
    - `cargo test -p codex-tui
    resize_reflow_wraps_transcript_early_when_pet_is_enabled`
    - `cargo insta pending-snapshots`
  • fix(tui): clear first inline viewport render (#21450)
    ## Why
    
    The alpha TUI can render the initial trust-directory prompt with stale
    terminal text showing through spaces when startup begins below existing
    shell output. The first inline viewport transition can happen while the
    previous viewport is still empty, so the old clear path no-ops before
    Ratatui draws the prompt. Ratatui then skips blank cells because its
    previous buffer also thinks those cells are blank, leaving old terminal
    contents visible inside the prompt.
    
    ## What Changed
    
    - Clear from the new inline viewport top when the previous viewport is
    empty during a viewport transition.
    - Keep the existing clear-from-old-viewport behavior for normal viewport
    updates.
    - Add a VT100-backed regression test that pre-fills terminal contents,
    performs the first viewport clear, and verifies stale text inside the
    new viewport is removed while shell content above the viewport remains.
    
    ## How to Test
    
    1. Start Codex alpha in a terminal that already has visible shell output
    above the cursor.
    2. Use a fresh untrusted project directory so the trust-directory prompt
    appears.
    3. Confirm the prompt text renders cleanly, with spaces staying blank
    instead of showing fragments of previous shell output.
    4. As a regression check, confirm content above the inline viewport is
    still preserved in terminal scrollback.
    
    Targeted tests:
    - `cargo test -p codex-tui
    first_viewport_change_clears_from_new_viewport_when_old_viewport_is_empty
    -- --nocapture`
    - `cargo test -p codex-tui`
  • feat(tui): add raw scrollback mode (#20819)
    ## Why
    
    Granular copy is particularly difficult with the current output. Part of
    it was solved with the introduction of the `/copy` command but when you
    only need to copy parts of a response, you still encounter some issues:
    
    - When you copy a paragraph, the result is a sequence of separate lines
    instead of one correctly joined paragraph.
    - When a word wraps, part of it stays on the original line and the rest
    appears at the start of the next line.
    - When you copy a long command, extra line breaks are often inserted,
    and command arguments can be split across multiple lines.
    
    
    https://github.com/user-attachments/assets/0ef85c84-9363-4aad-b43a-15fce062a443
    
    ## Solution
    
    Now that we own the scrollback and we re-create it when we resize, we
    have the opportunity of toggling between the raw text and the rich text
    we see today.
    
    - Add TUI raw scrollback mode with `tui.raw_output_mode`, `/raw
    [on|off]`, and the configurable `tui.keymap.global.toggle_raw_output`
    action.
    - Render transcript cells through rich/raw-aware paths so raw mode
    preserves source text and lets the terminal soft-wrap selection-friendly
    output.
    - Bind raw-mode toggle to `alt-r` by default, with the keybinding path
    toggling silently while `/raw` continues to emit confirmation messages.
    
    ## Related Issues
    
    Likely addressed by raw mode:
    
    - #12200: clean copy for multiline and soft-wrapped output. Raw mode
    removes Codex-inserted wrapping/indentation and lets the terminal
    soft-wrap logical lines.
    - #9252: command suggestions gain unwanted leading spaces when copied.
    Raw mode renders transcript text without the rich-mode left
    padding/gutter.
    - #8258: prompt output is hard to copy because of leading indentation.
    Raw mode renders user/source-backed transcript text without that
    decorative indentation.
    
    Partially or conditionally addressed:
    
    - #2880: copy/export message as Markdown. Raw mode exposes raw Markdown
    for terminal selection, but this PR does not add a dedicated
    export/copy-message command.
    - #19820: mouse drag selection + copy in the TUI. Raw mode improves
    terminal-native selection of output/history text, but this PR does not
    implement in-TUI mouse selection, highlighting, auto-copy, or composer
    selection.
    - #18979: copied content is divided into two parts. This should improve
    cases caused by Codex-inserted wraps/padding in rendered output; if the
    report is about pasting into the composer/input path, that remains
    outside this PR.
    
    ## Validation
    
    - `just write-config-schema`
    - `just fmt`
    - `cargo test -p codex-config`
    - `cargo test -p codex-tui`
    - `just fix -p codex-tui`
    - `just argument-comment-lint`
    - `cargo test -p codex-tui
    raw_output_mode_can_change_without_inserting_notice -- --nocapture`
    - `cargo test -p codex-tui
    raw_slash_command_toggles_and_accepts_on_off_args -- --nocapture`
    - `cargo test -p codex-tui raw_output_toggle -- --nocapture`
    - `git diff --check`
    - `cargo insta pending-snapshots`
  • fix(tui): bound startup terminal probes (#20654)
    ## Summary
    
    Bound TUI startup terminal response probes so unsupported terminals
    cannot stall startup for multiple seconds.
    
    This replaces the Unix startup uses of crossterm's blocking response
    probes with short `/dev/tty` probes that use nonblocking reads and
    `poll` with a 100ms timeout. It covers the initial cursor-position
    query, keyboard enhancement support detection, and OSC 10/11
    default-color detection. The default-color probe uses one shared
    deadline for foreground and background instead of allowing two
    independent full waits.
    
    The diagnostic mode/trace env vars from the investigation branch are
    intentionally not included. The shipped behavior is simply bounded
    probing by default, while non-Unix keeps the existing crossterm fallback
    path.
    
    ## Details
    
    - Add a private `terminal_probe` module for bounded Unix terminal probes
    and response parsers.
    - Let `custom_terminal::Terminal` accept a caller-provided initial
    cursor position so startup can compute it before constructing the
    terminal.
    - Use bounded cursor, keyboard enhancement, and default-color probes on
    Unix startup.
    - Preserve default-color cache behavior so a failed attempted query does
    not retry forever.
    
    ## Validation
    
    - `cd codex-rs && just fmt`
    - `cd codex-rs && cargo test -p codex-tui terminal_probe`
    - `cd codex-rs && just fix -p codex-tui`
    - `cd codex-rs && just argument-comment-lint`
    - `git diff --check`
    - `git diff --cached --check`
    
    `cd codex-rs && cargo test -p codex-tui` still aborts on the
    pre-existing local stack overflow in
    `app::tests::discard_side_thread_keeps_local_state_when_server_close_fails`;
    I reproduced that same focused failure on `main` before this PR work, so
    it is not introduced by this change.
    
    Manual validation in the VM showed the original crossterm path taking
    about 2s per unanswered probe, while bounded probing returned in about
    100ms per probe.
  • feat(tui): add vim composer mode (#18595)
    ## Why
    
    Codex now has configurable TUI keymaps, but the composer still behaves
    like a plain text field. Users who prefer modal editing need a way to
    keep Vim muscle memory while drafting prompts, and the keymap picker
    needs to expose Vim-specific actions if those bindings are configurable
    instead of hardcoded.
    
    ## What Changed
    
    - Adds composer Vim mode with insert/normal state, common normal-mode
    movement and editing commands, `d`/`y` operator-pending flows, and
    mode-aware footer and cursor indicators.
    - Adds `/vim`, an optional global `toggle_vim_mode` binding, and
    `tui.vim_mode_default` so Vim mode can be toggled per session or enabled
    as the default composer state.
    - Extends runtime and config keymaps with `vim_normal` and
    `vim_operator` contexts, exposes those contexts in `/keymap`, refreshes
    the config schema, and validates Vim bindings separately.
    - Integrates Vim normal mode with existing composer behavior: `/` opens
    slash command entry, `!` enters shell mode, `j`/`k` navigate history at
    history boundaries, successful submissions reset back to normal mode,
    and paste burst handling remains insert-mode only.
    - Teaches the TUI render path to apply and restore cursor style so Vim
    insert mode can use a bar cursor without leaving the terminal in that
    state after exit.
    
    ## Validation
    
    - `cargo test -p codex-tui keymap -- --nocapture` on the keymap/Vim
    coverage
    - `cargo insta pending-snapshots`
    
    ## Docs
    
    This introduces user-facing `/vim`, `tui.vim_mode_default`, and Vim
    keymap contexts under `tui.keymap`, so the public CLI configuration and
    slash-command docs should be updated before the feature ships.
  • Reset TUI keyboard reporting on exit (#19625)
    ## Why
    
    Codex enables enhanced keyboard reporting while the TUI owns the
    terminal. In iTerm2, exiting the TUI with Ctrl+C can intermittently
    leave the parent shell receiving raw CSI-u / `modifyOtherKeys` fragments
    instead of normal key input.
    
    Final terminal cleanup should put the parent shell back into normal
    keyboard reporting even if the terminal misses the usual stack pop.
    
    Fixes #19553.
    
    ## What Changed
    
    - Move TUI keyboard enhancement setup and detection into
    `tui/src/tui/keyboard_modes.rs`.
    - Add an exit-only `restore_after_exit()` path that performs the normal
    keyboard enhancement pop plus unconditional keyboard enhancement and
    `modifyOtherKeys` resets.
    - Keep temporary restore paths, such as external-editor handoff, using
    the balanced stack pop behavior.
    
    ## Confidence
    
    Medium. This is a speculative fix: I was not able to reproduce the
    reported iTerm2 behavior manually, but the symptoms line up with
    terminal keyboard reporting state surviving Codex exit. The added reset
    sequences are scoped to final TUI shutdown and should be harmless when
    the terminal is already clean.
  • fix(tui): reflow scrollback on terminal resize (#18575)
    Fixes multiple scrollback and terminal resize issues: #5538, #5576,
    #8352, #12223, #16165, and #15380.
    
    ## Why
    
    Codex writes finalized transcript output into terminal scrollback after
    wrapping it for the current viewport width. A later terminal resize
    could leave that scrollback shaped for the old width, so wider windows
    kept narrow output and narrower windows could show stale wrapping
    artifacts until enough new output replaced the visible area.
    
    This is also the foundation PR for responsive markdown tables. Table
    rendering needs finalized transcript content to be width-sensitive after
    insertion, not only while content is first streaming. Markdown table
    rendering itself stays in #18576.
    
    ## Stack
    
    - PR1: resize backlog reflow and interrupt cleanup
    - #18576: markdown table support
    
    ## What Changed
    
    - Rebuild source-backed transcript history when the terminal width
    changes. `terminal_resize_reflow` is introduced through the experimental
    feature system, but is enabled by default for this rollout so we can
    validate behavior across real terminals.
    - Preserve assistant and plan stream source so finalized streaming
    output can participate in resize reflow after consolidation.
    - Debounce resize work, but force a final source-backed reflow when a
    resize happened during active or unconsolidated streaming output.
    - Clear stale pending history lines on resize so old-width wrapped
    output is not emitted just before rebuilt scrollback.
    - Bound replay work with `[tui.terminal_resize_reflow].max_rows`:
    omitted uses terminal-specific defaults, `0` keeps all rendered rows,
    and a positive value sets an explicit cap. The cap applies both while
    initially replaying a resumed transcript into scrollback and when
    rebuilding scrollback after terminal resize.
    - Consolidate interrupted assistant streams before cleanup, then clear
    pending stream output and active-tail state consistently.
    - Move resize reflow and thread event buffering helpers out of `app.rs`
    into dedicated TUI modules.
    - Add focused coverage for resize reflow, feature-gated behavior,
    streaming source preservation, interrupted output cleanup,
    unicode-neutral text, terminal-specific row caps, and composer/layout
    stability.
    
    ## Runtime Bounds
    
    Resize reflow keeps only the most recent rendered rows when a row cap is
    active. The default is `auto`, which maps to the detected terminal's
    default scrollback size where Codex can identify it: VS Code `1000`,
    Windows Terminal `9001`, WezTerm `3500`, and Alacritty `10000`.
    Terminals without a dedicated mapping use the conservative fallback of
    `1000` rows. Users can override this with `[tui.terminal_resize_reflow]
    max_rows = N`, or set `max_rows = 0` to disable row limiting.
    
    ## Validation
    
    - `just fmt`
    - `git diff --check`
    - `cargo test --manifest-path codex-rs/Cargo.toml -p codex-tui reflow`
    - `cargo test --manifest-path codex-rs/Cargo.toml -p codex-tui
    transcript_reflow`
    - `just fix -p codex-tui`
    - PR CI in progress on the squashed branch
  • fix(tui): disable enhanced keys for VS Code WSL (#18741)
    Fixes https://github.com/openai/codex/issues/13638
    
    ## Why
    
    VS Code's integrated terminal can run a Linux shell through WSL without
    exposing `TERM_PROGRAM` to the Linux process, and with crossterm
    keyboard enhancement flags enabled that environment can turn dead-key
    composition into malformed key events instead of composed Unicode input.
    Codex already handles composed Unicode correctly, so the fix is to avoid
    enabling the terminal mode that breaks this path for the affected
    terminal combination.
    
    ## What Changed
    
    - Automatically skip crossterm keyboard enhancement flags when Codex
    detects WSL plus VS Code, including a Windows-side `TERM_PROGRAM` probe
    through WSL interop.
    - Add `CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT` so users can
    force-disable or force-enable the keyboard enhancement policy for
    diagnosis.
    
    ## Verification
    
    - Added unit coverage for env parsing, VS Code detection, and the WSL/VS
    Code auto-disable policy.
    - `cargo check -p codex-tui` passed.
    - `./tools/argument-comment-lint/run.py -p codex-tui -- --tests` passed.
    - `cargo test -p codex-tui` was attempted locally, but the checkout
    failed during linking before tests executed because V8 symbols from
    `codex-code-mode` were unresolved for `arm64`.
  • Add TUI notification condition config (#17175)
    Problem: TUI desktop notifications are hard-gated on terminal focus, so
    terminal/IDE hosts that want in-focus notifications cannot opt in.
    
    Solution: Add a flat `[tui] notification_condition` setting (`unfocused`
    by default, `always` opt-in), carry grouped TUI notification settings
    through runtime config, apply method + condition together in the TUI,
    and regenerate the config schema.
  • feat: single app-server bootstrap in TUI (#16582)
    Before this, the TUI was starting 2 app-server. One to check the login
    status and one to actually start the session
    
    This PR make only one app-server startup and defer the login check in
    async, outside of the frame rendering path
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix: address unused variable on windows (#16633)
    This slipped in during https://github.com/openai/codex/pull/16578. I am
    still working on getting Windows working properly with Bazel on PRs.
  • fix(tui): handle zellij redraw and composer rendering (#16578)
    ## TL;DR
    
    Fixes the issues when using Codex CLI with Zellij multiplexer. Before
    this PR there would be no scrollback when using it inside a zellij
    terminal.
    
    ## Problem
    
    Addresses #2558
    
    Zellij does not support ANSI scroll-region manipulation (`DECSTBM` /
    Reverse Index) or the alternate screen buffer in the way traditional
    terminals do. When codex's TUI runs inside Zellij, two things break: (1)
    inline history insertion corrupts the display because the scroll-region
    escape sequences are silently dropped or mishandled, and (2) the
    composer textarea renders with inherited background/foreground styles
    that produce unreadable text against Zellij's pane chrome.
    
    ## Mental model
    
    The fix introduces a **Zellij mode** — a runtime boolean detected once
    at startup via `codex_terminal_detection::terminal_info().is_zellij()` —
    that gates two subsystems onto Zellij-safe terminal strategies:
    
    - **History insertion** (`insert_history.rs`): Instead of using
    `DECSTBM` scroll regions and Reverse Index (`ESC M`) to slide content
    above the viewport, Zellij mode scrolls the screen by emitting `\n` at
    the bottom row and then writes history lines at absolute positions. This
    avoids every escape sequence Zellij mishandles.
    - **Viewport expansion** (`tui.rs`): When the viewport grows taller than
    available space, the standard path uses `scroll_region_up` on the
    backend. Zellij mode instead emits newlines at the screen bottom to push
    content up, then invalidates the ratatui diff buffer so the next draw is
    a full repaint.
    - **Composer rendering** (`chat_composer.rs`, `textarea.rs`): All text
    rendering in the input area uses an explicit `base_style` with
    `Color::Reset` foreground, preventing Zellij's pane styling from
    bleeding into the textarea. The prompt chevron (`›`) and placeholder
    text use explicit color constants instead of relying on `.bold()` /
    `.dim()` modifiers that render inconsistently under Zellij.
    
    ## Non-goals
    
    - This change does not fix or improve Zellij's terminal emulation
    itself.
    - It does not rearchitect the inline viewport model; it adds a parallel
    code path gated on detection.
    - It does not touch the alternate-screen disable logic (that already
    existed and continues to use `is_zellij` via the same detection).
    
    ## Tradeoffs
    
    - **Code duplication in `insert_history.rs`**: The Zellij and Standard
    branches share the line-rendering loop (color setup, span merging,
    `write_spans`) but differ in the scrolling preamble. The duplication is
    intentional — merging them would force a complex conditional state
    machine that's harder to reason about than two flat sequences.
    - **`invalidate_viewport` after every Zellij history flush or viewport
    expansion**: This forces a full repaint on every draw cycle in Zellij,
    which is more expensive than ratatui's normal diff-based rendering. This
    is necessary because Zellij's lack of scroll-region support means the
    diff buffer's assumptions about what's on screen are invalid after we
    manually move content.
    - **Explicit colors vs semantic modifiers**: Replacing `.bold()` /
    `.dim()` with `Color::Cyan` / `Color::DarkGray` / `Color::White` in the
    Zellij branch sacrifices theme-awareness for correctness. If the project
    ever adopts a theming system, Zellij styling will need to participate.
    
    ## Architecture
    
    The Zellij detection flag flows through three layers:
    
    1. **`codex_terminal_detection`** — `TerminalInfo::is_zellij()` (new
    convenience method) reads the already-detected `Multiplexer` variant.
    2. **`Tui` struct** — caches `is_zellij` at construction; passes it into
    `update_inline_viewport`, `flush_pending_history_lines`, and
    `insert_history_lines_with_mode`.
    3. **`ChatComposer` struct** — independently caches `is_zellij` at
    construction; uses it in `render_textarea` for style decisions.
    
    The two caches (`Tui.is_zellij` and `ChatComposer.is_zellij`) are read
    from the same global `OnceLock<TerminalInfo>`, so they always agree.
    
    ## Observability
    
    No new logging, metrics, or tracing is introduced. Diagnosis depends on:
    - Whether `ZELLIJ` or `ZELLIJ_SESSION_NAME` env vars are set (the
    detection heuristic).
    - Visual inspection of the rendered TUI inside Zellij vs a standard
    terminal.
    - The insta snapshot `zellij_empty_composer` captures the Zellij-mode
    render path.
    
    ## Tests
    
    - `terminal_info_reports_is_zellij` — unit test in `terminal-detection`
    confirming the convenience method.
    - `zellij_empty_composer_snapshot` — insta snapshot in `chat_composer`
    validating the Zellij render path for an empty composer.
    - `vt100_zellij_mode_inserts_history_and_updates_viewport` — integration
    test in `insert_history` verifying that Zellij-mode history insertion
    writes content and shifts the viewport.
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
  • [codex] Remove codex-core config type shim (#16529)
    ## Why
    
    This finishes the config-type move out of `codex-core` by removing the
    temporary compatibility shim in `codex_core::config::types`. Callers now
    depend on `codex-config` directly, which keeps these config model types
    owned by the config crate instead of re-expanding `codex-core` as a
    transitive API surface.
    
    ## What Changed
    
    - Removed the `codex-rs/core/src/config/types.rs` re-export shim and the
    `core::config::ApprovalsReviewer` re-export.
    - Updated `codex-core`, `codex-cli`, `codex-tui`, `codex-app-server`,
    `codex-mcp-server`, and `codex-linux-sandbox` call sites to import
    `codex_config::types` directly.
    - Added explicit `codex-config` dependencies to downstream crates that
    previously relied on the `codex-core` re-export.
    - Regenerated `codex-rs/core/config.schema.json` after updating the
    config docs path reference.
  • Rename tui_app_server to tui (#16104)
    This is a follow-up to https://github.com/openai/codex/pull/15922. That
    previous PR deleted the old `tui` directory and left the new
    `tui_app_server` directory in place. This PR renames `tui_app_server` to
    `tui` and fixes up all references.
  • Remove the legacy TUI split (#15922)
    This is the part 1 of 2 PRs that will delete the `tui` /
    `tui_app_server` split. This part simply deletes the existing `tui`
    directory and marks the `tui_app_server` feature flag as removed. I left
    the `tui_app_server` feature flag in place for now so its presence
    doesn't result in an error. It is simply ignored.
    
    Part 2 will rename the `tui_app_server` directory `tui`. I did this as
    two parts to reduce visible code churn.
  • feat(tui) /clear (#12444)
    # /clear feature! 
    
    /clear will clear your terminal while preserving the context/state of
    the thread.
  • fix(tui): flush input buffer on init to prevent early exit on Windows (#10729)
    Fixes #10661.
    
    ### Problem
    On Windows, the sign-in menu can exit immediately if the OS-level input
    buffer contains trailing characters (like the Enter key from running the
    command).
    
    ### Solution
    **Flush Input Buffer on Init**: Use FlushConsoleInputBuffer on Windows
    (and cflush on Unix) in ui::init() to discard any input captured before
    the TUI was ready.
    
    Verified by @CodebyAmbrose in #10661.
  • feat(tui): pace catch-up stream chunking with hysteresis (#10461)
    ## Summary
    - preserve baseline streaming behavior (smooth mode still commits one
    line per 50ms tick)
    - extract adaptive chunking policy and commit-tick orchestration from
    ChatWidget into `streaming/chunking.rs` and `streaming/commit_tick.rs`
    - add hysteresis-based catch-up behavior with bounded batch draining to
    reduce queue lag without bursty single-frame jumps
    - document policy behavior, tuning guidance, and debug flow in rustdoc +
    docs
    
    ## Testing
    - just fmt
    - cargo test -p codex-tui
  • Added tui.notifications_method config option (#10043)
    This PR adds a new `tui.notifications_method` config option that accepts
    values of "auto", "osc9" and "bel". It defaults to "auto", which
    attempts to auto-detect whether the terminal supports OSC 9 escape
    sequences and falls back to BEL if not.
    
    The PR also removes the inconsistent handling of notifications on
    Windows when WSL was used.
  • fix: add tui.alternate_screen config and --no-alt-screen CLI flag for Zellij scrollback (#8555)
    Fixes #2558
    
    Codex uses alternate screen mode (CSI 1049) which, per xterm spec,
    doesn't support scrollback. Zellij follows this strictly, so users can't
    scroll back through output.
    
    **Changes:**
    - Add `tui.alternate_screen` config: `auto` (default), `always`, `never`
    - Add `--no-alt-screen` CLI flag
    - Auto-detect Zellij and skip alt screen (uses existing `ZELLIJ` env var
    detection)
    
    **Usage:**
    ```bash
    # CLI flag
    codex --no-alt-screen
    
    # Or in config.toml
    [tui]
    alternate_screen = "never"
    ```
    
    With default `auto` mode, Zellij users get working scrollback without
    any config changes.
    
    ---------
    
    Co-authored-by: Josh McKinney <joshka@openai.com>
  • perf(tui): cap redraw scheduling to 60fps (#8499)
    Clamp frame draw notifications in the `FrameRequester` scheduler so we
    don't redraw more frequently than a user can perceive.
    
    This applies to both `codex-tui` and `codex-tui2`, and keeps the
    draw/dispatch loops simple by centralizing the rate limiting in a small
    helper module.
    
    - Add `FrameRateLimiter` (pure, unit-tested) to clamp draw deadlines
    - Apply the limiter in the scheduler before emitting `TuiEvent::Draw`
    - Use immediate redraw requests for scroll paths (scheduler now
    coalesces + clamps)
    - Add scheduler tests covering immediate/delayed interactions
  • feat: open prompt in configured external editor (#7606)
    Add `ctrl+g` shortcut to enable opening current prompt in configured
    editor (`$VISUAL` or `$EDITOR`).
    
    
    - Prompt is updated with editor's content upon editor close.
    - Paste placeholders are automatically expanded when opening the
    external editor, and are not "recompressed" on close
    - They could be preserved in the editor, but it would be hard to prevent
    the user from modifying the placeholder text directly, which would drop
    the mapping to the `pending_paste` value
    - Image placeholders stay as-is
    - `ctrl+g` explanation added to shortcuts menu, snapshot tests updated
    
    
    
    https://github.com/user-attachments/assets/4ee05c81-fa49-4e99-8b07-fc9eef0bbfce
  • Make loading malformed skills fail-open (#8243)
    Instead of failing to start Codex, clearly call out that N skills did
    not load and provide warnings so that the user may fix them.
    
    <img width="3548" height="874" alt="image"
    src="https://github.com/user-attachments/assets/6ce041b2-1373-4007-a6dd-0194e58fafe4"
    />
  • Add public skills + improve repo skill discovery and error UX (#8098)
    1. Adds SkillScope::Public end-to-end (core + protocol) and loads skills
    from the public cache directory
    2. Improves repo skill discovery by searching upward for the nearest
    .codex/skills within a git repo
    3. Deduplicates skills by name with deterministic ordering to avoid
    duplicates across sources
    4. Fixes garbled “Skill errors” overlay rendering by preventing pending
    history lines from being injected during the modal
    5. Updates the project docs “Skills” intro wording to avoid hardcoded
    paths
  • refactor TUI event loop to enable dropping + recreating crossterm event stream (#7961)
    Introduces an `EventBroker` between the crossterm `EventStream` source
    and the consumers in the TUI. This enables dropping + recreating the
    `crossterm_events` without invalidating the consumer.
    
    Dropping and recreating the crossterm event stream enables us to fully
    relinquish `stdin` while the app keeps running. If the stream is not
    dropped, it will continue to read from `stdin` even when it is not
    actively being polled, potentially stealing input from other processes.
    See
    [here](https://www.reddit.com/r/rust/comments/1f3o33u/myterious_crossterm_input_after_running_vim/?utm_source=chatgpt.com)
    and [here](https://ratatui.rs/recipes/apps/spawn-vim/) for details.
    
    ### Tests
    Added tests for new `EventBroker` setup, existing tests pass, tested
    locally.
  • fix: break tui (#7876)
    Prevent TUI to loop for ever if one of the RX it's listing on get closed
  • Fix toasts on Windows under WSL 2 (#7137)
    Before this: no notifications or toasts when using Codex CLI in WSL 2.
    
    After this: I get toasts from Codex
  • refactor: tui.rs extract several pieces (#7461)
    Pull FrameRequester out of tui.rs into its own module and make a
    FrameScheduler struct. This is effectively an Actor/Handler approach
    (see https://ryhl.io/blog/actors-with-tokio/). Adds tests and docs.
    
    Small refactor of pending_viewport_area logic.
  • fix(tui): Fail when stdin is not a terminal (#6382)
    Piping to codex fails to do anything useful and locks up the process.
    We currently check for stdout, but not stdin
    
    ```
    ❯ echo foo|just c
    cargo run --bin codex -- "$@"
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.21s
         Running `target/debug/codex`
    Error: stdin is not a terminal
    error: Recipe `codex` failed on line 10 with exit code 1
    ```
  • refactor(tui): job-control for Ctrl-Z handling (#6477)
    - Moved the unix-only suspend/resume logic into a dedicated job_control
    module housing SuspendContext, replacing scattered cfg-gated fields and
    helpers in tui.rs.
    - Tui now holds a single suspend_context (Arc-backed) instead of
    multiple atomics, and the event stream uses it directly for Ctrl-Z
    handling.
    - Added detailed docs around the suspend/resume flow, cursor tracking,
    and the Arc/atomic ownership model for the 'static event stream.
    - Renamed the process-level SIGTSTP helper to suspend_process and the
    cursor tracker to set_cursor_y to better reflect their roles.
  • fix(tui): propagate errors in insert_history_lines_to_writer (#4266)
    ## What?
    Fixed error handling in `insert_history_lines_to_writer` where all
    terminal operations were silently ignoring errors via `.ok()`.
    
      ## Why?
    Silent I/O failures could leave the terminal in an inconsistent state
    (e.g., scroll region not reset) with no way to debug. This violates Rust
    error handling best practices.
    
      ## How?
      - Changed function signature to return `io::Result<()>`
      - Replaced all `.ok()` calls with `?` operator to propagate errors
    - Added `tracing::warn!` in wrapper function for backward compatibility
      - Updated 15 test call sites to handle Result  with `.expect()`
    
      ## Testing
      -  Pass all tests
    
      ## Type of Change
      - [x] Bug fix (non-breaking change)
    
    ---------
    
    Signed-off-by: Huaiwu Li <lhwzds@gmail.com>
    Co-authored-by: Eric Traut <etraut@openai.com>
  • tui: hardcode xterm palette, shimmer blends between fg and bg (#4957)
    Instead of querying all 256 terminal colors on startup, which was slow
    in some terminals, hardcode the default xterm palette.
    
    Additionally, tweak the shimmer so that it blends between default_fg and
    default_bg, instead of "dark gray" (according to the terminal) and pure
    white (regardless of terminal theme).
  • requery default colors on focus (#4673)
    fixes an issue when terminals change their color scheme, e.g. dark/light
    mode, the composer wouldn't update its background color.
  • update composer + user message styling (#4240)
    Changes:
    
    - the composer and user messages now have a colored background that
    stretches the entire width of the terminal.
    - the prompt character was changed from a cyan `▌` to a bold `›`.
    - the "working" shimmer now follows the "dark gray" color of the
    terminal, better matching the terminal's color scheme
    
    | Terminal + Background        | Screenshot |
    |------------------------------|------------|
    | iTerm with dark bg | <img width="810" height="641" alt="Screenshot
    2025-09-25 at 11 44 52 AM"
    src="https://github.com/user-attachments/assets/1317e579-64a9-4785-93e6-98b0258f5d92"
    /> |
    | iTerm with light bg | <img width="845" height="540" alt="Screenshot
    2025-09-25 at 11 46 29 AM"
    src="https://github.com/user-attachments/assets/e671d490-c747-4460-af0b-3f8d7f7a6b8e"
    /> |
    | iTerm with color bg | <img width="825" height="564" alt="Screenshot
    2025-09-25 at 11 47 12 AM"
    src="https://github.com/user-attachments/assets/141cda1b-1164-41d5-87da-3be11e6a3063"
    /> |
    | Terminal.app with dark bg | <img width="577" height="367"
    alt="Screenshot 2025-09-25 at 11 45 22 AM"
    src="https://github.com/user-attachments/assets/93fc4781-99f7-4ee7-9c8e-3db3cd854fe5"
    /> |
    | Terminal.app with light bg | <img width="577" height="367"
    alt="Screenshot 2025-09-25 at 11 46 04 AM"
    src="https://github.com/user-attachments/assets/19bf6a3c-91e0-447b-9667-b8033f512219"
    /> |
    | Terminal.app with color bg | <img width="577" height="367"
    alt="Screenshot 2025-09-25 at 11 45 50 AM"
    src="https://github.com/user-attachments/assets/dd7c4b5b-342e-4028-8140-f4e65752bd0b"
    /> |
  • Cache keyboard enhancement detection before event streams (#3950)
    Hopefully fixes incorrectly showing ^J instead of Shift+Enter in the key
    hints occasionally.
  • notifications on approvals and turn end (#3329)
    uses OSC 9 to notify when a turn ends or approval is required. won't
    work in vs code or terminal.app but iterm2/kitty/wezterm supports it :)
  • refactor: remove AttachImage tui event (#3191)
    TuiEvent is supposed to be purely events that come from the "driver",
    i.e. events from the terminal. Everything app-specific should be an
    AppEvent. In this case, it didn't need to be an event at all.
  • tui: fix occasional UI flicker (#2918)
    occasionally i was seeing some minor flickering when adding history
    lines. hopefully this clears it up.
  • tui: catch get_cursor_position errors (#2870)
    still seeing errors with reading back the cursor position in some cases;
    adding catches everywhere we might run into this.
  • fix cursor after suspend (#2690)
    This was supposed to be fixed by #2569, but I think the actual fix got
    lost in the refactoring.
    
    Intended behavior: pressing ^Z moves the cursor below the viewport
    before suspending.
  • enable alternate scroll in transcript mode (#2686)
    this allows the mouse wheel to scroll the transcript / diff views.
  • tui: fix resize on wezterm (#2600)
    WezTerm doesn't respond to cursor queries during a synchronized update,
    so resizing was broken there.
  • ctrl+v image + @file accepts images (#1695)
    allow ctrl+v in TUI for images + @file that are images are appended as
    raw files (and read by the model) rather than pasted as a path that
    cannot be read by the model.
    
    Re-used components and same interface we're using for copying pasted
    content in
    https://github.com/openai/codex/commit/72504f1d9c6eb17086d86ef1fb0d17676812461b.
    @aibrahim-oai as you've implemented this, mind having a look at this
    one?
    
    
    https://github.com/user-attachments/assets/c6c1153b-6b32-4558-b9a2-f8c57d2be710
    
    ---------
    
    Co-authored-by: easong-openai <easong@openai.com>
    Co-authored-by: Daniel Edrisian <dedrisian@openai.com>
    Co-authored-by: Michael Bolin <mbolin@openai.com>
  • improve suspend behavior (#2569)
    This is a somewhat roundabout way to fix the issue that pressing ^Z
    would put the shell prompt in the wrong place (overwriting some of the
    status area below the composer). While I'm at it, clean up the suspend
    logic and fix some suspend-while-in-alt-screen behavior too.
  • scroll instead of clear on boot (#2535)
    this actually works fine already in iterm without this change, but
    Terminal.app adds a bunch of excess whitespace when we clear all.
    
    
    https://github.com/user-attachments/assets/c5bd1809-c2ed-4daa-a148-944d2df52876
  • refactor onboarding screen to a separate "app" (#2524)
    this is in preparation for adding more separate "modes" to the tui, in
    particular, a "transcript mode" to view a full history once #2316 lands.
    
    1. split apart "tui events" from "app events".
    2. remove onboarding-related events from AppEvent.
    3. move several general drawing tools out of App and into a new Tui
    class