Commit Graph

131 Commits

  • fix(tui): highlight C++ module files (#28554)
    ## Why
    
    Codex syntax-highlights diffs for conventional C++ extensions such as
    `.cpp` and `.cxx`, but C++ module interface files using `.cppm`, `.ixx`,
    or `.cxxm` fall back to plain diff coloring. The bundled syntax set
    already includes C++, but it does not resolve those module extensions by
    itself.
    
    Closes #28223.
    
    ## What changed
    
    - map `.cppm`, `.ixx`, and `.cxxm` to the existing `cpp` syntax in
    `render/highlight.rs`
    - extend alias-resolution coverage for all three module extensions
    - verify `.cpp`, `.cppm`, `.ixx`, and `.cxxm` diffs produce
    syntax-highlighted RGB spans while unknown extensions retain the plain
    fallback
    - snapshot the syntax-colored token segmentation for the supported C++
    module extensions
    
    ## How to Test
    
    1. Ask Codex to create or modify a C++ module interface file using
    `.cppm`, `.ixx`, or `.cxxm`.
    2. Confirm C++ tokens in the rendered diff receive syntax colors instead
    of only the red/green diff treatment.
    3. Modify an equivalent `.cpp` file and confirm its existing
    highlighting remains unchanged.
    4. Modify a file with an unknown extension and confirm it still uses the
    plain diff fallback.
    
    Targeted tests:
    
    - `just test -p codex-tui -E
    'test(find_syntax_resolves_languages_and_aliases) |
    test(cpp_module_extensions_use_cpp_highlighting) |
    test(unknown_extension_falls_back_without_syntax_highlighting)'`
  • feat(app-server): enforce managed remote control disable (#27961)
    ## Why
    
    Managed deployments need a reliable deny gate for remote control.
    Persisted enablement and explicit startup requests currently remain able
    to start the transport, while the removed `features.remote_control` key
    is intentionally only a compatibility no-op.
    
    This adds a dedicated requirement that administrators can use to force
    remote control off without deleting the user's persisted preference.
    Removing the requirement and restarting restores the prior choice.
    
    ## What Changed
    
    - Added top-level `allow_remote_control` requirements parsing, sourced
    layer precedence, debug output, and `configRequirements/read` exposure
    as `allowRemoteControl`.
    - Added a typed transport policy captured from the startup requirements
    snapshot. Managed disable forces the initial state to disabled and
    prevents enrollment, refresh, connection, and persisted-preference
    mutation.
    - Rejected every `remoteControl/*` RPC before parameter deserialization
    with JSON-RPC `-32600` and `remote control is disabled by managed
    requirements`.
    - Preserved the existing disabled status notification and the previous
    behavior when the requirement is `true` or omitted.
    - Regenerated app-server protocol schemas and documented the new
    requirement.
    
    ## Verification
    
    - Confirmed all remote-control RPCs, including a malformed request,
    return the managed-policy error while the initial status notification
    remains `disabled`.
    - Confirmed explicit ephemeral startup and persisted enablement make no
    backend connection and leave the SQLite preference unchanged.
    - Confirmed `allow_remote_control = true` does not enable or block
    remote control and `configRequirements/read` returns
    `allowRemoteControl: false` for the deny policy.
    
    Related issue: N/A (managed-policy hardening).
  • [codex] restore source-specific import copy (#27703)
    ## Summary
    
    - restore source-specific wording across the `/import` picker, lifecycle
    messages, diagnostics, and help text
    - update the matching Unix and Windows snapshots
    - leave import behavior unchanged
    
    ## Why
    
    The import path currently supports one source, so the UI should identify
    that source directly instead of presenting the flow as
    provider-agnostic.
    
    ## Validation
    
    - `just test -p codex-tui external_agent_config_migration` (12 passed)
    - `just fix -p codex-tui`
    - `just fmt`
  • [1 of 3] Support long raw TUI goal objectives (#27508)
    ## Stack
    
    1. **[1 of 3] Support long raw TUI goal objectives** - this PR
    2. [2 of 3] Support long pasted text in TUI goals - #27509
    3. [3 of 3] Support images in TUI goals - #27510
    
    ## Why
    
    `thread/goal/set` limits persisted objective text to 4000 characters.
    The TUI used to reject raw `/goal` objectives above that limit, even
    though the client can make them usable by writing the long text to a
    file and storing a short objective that points at that file.
    
    This also needs to work for remote app-server sessions: filesystem API
    calls must create files on the app-server host, and the stored path must
    be meaningful to the agent on that host.
    
    ## What Changed
    
    - Adds an app-server-host path helper so TUI code can build paths that
    are resolved on the app-server host rather than the TUI host.
    - Adds TUI app-server session helpers for `fs/createDirectory`,
    `fs/writeFile`, `fs/readFile`, and `fs/remove` that work for embedded
    and remote app-server sessions without changing the app-server protocol.
    - Materializes oversized raw `/goal` objectives into
    `$CODEX_HOME/attachments/<uuid>/goal-objective.md` through the
    app-server filesystem APIs, then stores a short, readable objective that
    directs the agent to that file.
    - Reads managed objective files back for `/goal edit`. Other goal UI
    renders the readable stored objective normally, without
    managed-file-specific presentation logic.
    - Recognizes managed references only when they name the expected
    generated file under the app server's reported `$CODEX_HOME`, and cleans
    up newly materialized files when goal replacement or setting does not
    complete.
    
    ## Verification
    
    - Added/updated TUI tests for raw oversized `/goal` submission, large
    inline-paste expansion, queued oversized goals, app-facing
    materialization before `thread/goal/set`, managed-path validation,
    editing, and cleanup.
    - Added/updated app-server-client remote coverage for initialized remote
    Codex home handling.
    
    ## Manual Testing
    
    - Ran the real TUI against a Unix-socket app server with different local
    and server `$CODEX_HOME` directories. Oversized goals wrote only under
    the server home, and persisted references used the server-canonical path
    rather than the TUI path.
    - Exercised 3,999-, 4,000-, and 4,001-character raw objectives. The
    first two stayed inline without new files; the 4,001-character objective
    became a managed objective file.
    - Submitted a larger 8,275-character objective, verified its full
    contents on the app-server host, and observed the goal continuation open
    the referenced server-side file.
    - Opened `/goal edit` for a managed objective and verified the full text
    was restored through remote `fs/readFile`.
    - Submitted an oversized replacement while a goal was active, verified
    no file was written before confirmation, then canceled and confirmed
    that the existing goal and attachment count were unchanged.
  • [codex] add /import for external agents (#27071)
    ## Why
    
    External-agent import should be discoverable and deliberate without
    blocking startup or claiming the public `codex [PROMPT]` CLI namespace.
    The slash command keeps the flow local to the interactive TUI and reuses
    the existing app-server import API.
    
    ## What changed
    
    - add the user-facing `/import` slash command
    - detect external-agent importable items only when the command is
    invoked
    - run imports through the embedded local app-server
    - show start and completion messages, refresh configuration, and block
    duplicate imports while one is pending
    - reject the flow for unsupported remote and local-daemon sessions
    
    ## Validation
    
    - `just test -p codex-tui external_agent_config_migration` (10 passed)
    - manually exercised an isolated TUI fixture with existing
    external-agent setup and session data using a fresh `CODEX_HOME`
    - verified picker customization, plugin and session detection, import
    completion, repeated invocation, and imported-session resume context
    - the broader `just test -p codex-tui` run passed 2,805 tests, with 2
    unrelated guardian feature-flag failures and 4 skipped tests
    
    ## Draft follow-ups
    
    - review whether completion messaging should remain attached to the
    initiating chat if the user switches chats during an import
    - review shutdown semantics for an in-progress background import
    
    ## Stack
    
    1. [#27064](https://github.com/openai/codex/pull/27064): remove the
    startup migration flow
    2. [#27065](https://github.com/openai/codex/pull/27065): extract the
    picker renderer
    3. [#27070](https://github.com/openai/codex/pull/27070): add the
    external-agent import picker UX
    4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow
    through `/import`
    
    **This PR is stack item 4.** Draft while the lower stack dependencies
    are reviewed.
  • [codex] add external agent import picker UX (#27070)
    ## Why
    
    Users need to understand what external-agent data Codex detected, what
    is selected, and how to proceed before an import begins. The updated
    picker makes focus, selection state, and the submission path explicit
    while preserving the existing import backend.
    
    ## What changed
    
    - replace the old migration prompt with a two-step external-agent import
    picker
    - add a customize view with explicit item focus, selection state,
    counts, and a review action
    - separate detected import data into a view model
    - add Unix and Windows snapshots for prompt, item-focus, and
    action-focus states
    
    ## Validation
    
    - `just test -p codex-tui external_agent_config_migration` (10 passed)
    - manually exercised an isolated TUI fixture covering customization,
    selection toggles, review, import, repeated invocation, and session
    resume
    - the broader `just test -p codex-tui` run passed 2,805 tests, with 2
    unrelated guardian feature-flag failures and 4 skipped tests
    
    ## Review note
    
    This is the largest layer in the stack because the interaction state,
    rendering changes, and required snapshots move together. It remains a
    draft in case reviewers prefer a further presentation/state split.
    
    ## Stack
    
    1. [#27064](https://github.com/openai/codex/pull/27064): remove the
    startup migration flow
    2. [#27065](https://github.com/openai/codex/pull/27065): extract the
    picker renderer
    3. [#27070](https://github.com/openai/codex/pull/27070): add the
    external-agent import picker UX
    4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow
    through `/import`
    
    **This PR is stack item 3.** Draft while the lower stack dependencies
    are reviewed.
  • Show effective sandbox modes in /debug-config (#27068)
    ## Summary
    - Render `/debug-config`'s `allowed_sandbox_modes` from the finalized
    permission constraints instead of the raw requirements list.
    - Add regression coverage for configured full-access and external
    sandbox modes being omitted when effective permissions reject them.
    
    ## Details
    `allowed_sandbox_modes` comes from managed requirements, but the final
    permissions can be further constrained by derived validation rules. For
    example, `permissions.filesystem.deny_read` requires sandbox
    enforcement, so modes that disable or externalize Codex's sandbox are
    not actually usable even if they were present in the raw requirements
    TOML.
    
    The debug renderer now enumerates the configured sandbox-mode labels and
    keeps only those accepted by `Config.permissions`. That makes
    `/debug-config` reflect the same effective permission-profile constraint
    path used by runtime config validation, while preserving the existing
    source/provenance display.
    
    ## Validation
    - Added a regression test for effective sandbox-mode filtering in
    `/debug-config`.
  • fix(tui): linkify complete bare URLs with tildes (#27088)
    ## Background
    
    Bare URLs containing `~` in their path are currently only clickable up
    to the tilde in the interactive TUI. For example, Codex renders the
    visible text for:
    
    
    `https://www.cs.tufts.edu/~nr/cs257/archive/olin-shivers/dissertation.pdf`
    
    but the OSC 8 destination stops at `https://www.cs.tufts.edu/`. This
    makes Cmd-click open the wrong location even though the terminal
    recognizes the complete URL outside Codex.
    
    Fixes #26774.
    
    ## Root Cause
    
    The URL scanner already accepts `~`. The truncation happens earlier:
    with strikethrough parsing enabled, `pulldown-cmark` splits this URL
    into adjacent decoded `Event::Text` values around the tilde. The
    Markdown renderer annotated each text event independently, so only the
    first event still looked like a complete URL with a supported scheme.
    
    The renderer now merges adjacent decoded text events before URL
    annotation. It preserves the combined source range while retaining
    parser-decoded contents, which avoids regressing entities such as
    `&amp;`.
    
    ## Changes
    
    - Add a small iterator that merges adjacent decoded Markdown text events
    and their source ranges.
    - Apply it at the Markdown renderer boundary before hyperlink detection.
    - Add regression coverage for the reported URL in prose, wrapped table
    output, and entity-decoded URLs.
    
    ## How to Test
    
    1. Run Codex with `just c`.
    2. Ask the assistant to output this exact bare URL with no Markdown link
    syntax:
    
    `https://www.cs.tufts.edu/~nr/cs257/archive/olin-shivers/dissertation.pdf`
    3. Hold Cmd and hover or click the URL.
    4. Confirm the complete URL, including the suffix after `~`, is one
    destination.
    5. Repeat with the URL inside a Markdown table and confirm wrapped
    portions retain the same complete destination.
    
    Targeted tests:
    
    - `just test -p codex-tui url_with_tilde`
    - `just test -p codex-tui merged_text_events_preserve_entity_decoding`
    
    The full `codex-tui` test run was also executed. Its only failures were
    the two existing Guardian feature-flag tests:
    
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
  • Render code comment directives in TUI replay (#26554)
    ## Summary
    
    Resumed Codex App or VS Code review sessions can contain
    `::code-comment` directives that the TUI previously displayed verbatim
    because only rich clients interpret them.
    
    This change rewrites valid line-start directives into readable Markdown
    during assistant-message parsing, using the session working directory
    for relative file paths. The fallback is applied consistently to live
    messages, replayed transcripts, and resume previews while preserving
    malformed directives and existing `::git-*` parsing.
    
    ## Before
    
    The TUI exposed the raw client directive:
    
    ```text
    ::code-comment{title="Fix body= parsing" body="Keep role=\"tab\", ::git-stage{cwd=/tmp}, file=, and \n literal." file="/repo/src/app.ts" start=10 end=12 priority="P2"}
    ```
    
    ## After
    
    The same directive is rendered as readable review feedback:
    
    ```text
    - [P2] Fix body= parsing — src/app.ts:10-12
      Keep role="tab", ::git-stage{cwd=/tmp}, file=, and \n literal.
    ```
    
    Fixes #25658
  • Surface TUI config write error causes (#26537)
    ## Summary
    
    TUI config writes currently wrap app-server failures with local context
    like `config/batchWrite failed in TUI`, but several user-visible paths
    only render the outer error. That hides the actionable app-server
    message, such as validation constraints or read-only `CODEX_HOME`
    failures, leaving users with a dead-end diagnostic.
    
    This change adds a small formatter next to the TUI config write helpers
    that renders the error source chain, then uses it for model persistence,
    feature persistence, project trust, status line writes, hook trust, and
    hook enablement.
    
    Fixes #26077
  • [codex] Fix Vim normal mode editing (#25022)
    ## Summary
    - add Vim normal-mode `s` support to substitute the character under the
    cursor and enter insert mode
    - fix Vim normal-mode `o` so opening below the final line moves the
    cursor onto the new blank line
    - update keymap config/schema and keymap picker snapshots for the new
    action
    
    ## Validation
    - `just fmt`
    - `just write-config-schema`
    - `just test -p codex-config`
    - focused `just test -p codex-tui` coverage for the Vim `s` and `o`
    behavior, keymap conflict handling, and keymap picker snapshots
    - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
    - `git diff --check`
    
    ## Notes
    A full `just test -p codex-tui` run still has two unrelated Guardian
    feature-flag failures in this checkout:
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
  • feat(tui): render cramped markdown tables as key-value records [2 of 2] (#24636)
    ## Stack
    
    - **Base: #24489 [1 of 2]** - render markdown tables in app style.
    - **Current: #24636 [2 of 2]** - render cramped markdown tables as
    key/value records.
    
    Review this PR against `fcoury/app-style-markdown-tables`; it contains
    only the fallback behavior for cramped tables.
    
    ## Why
    
    The row-separated markdown table rendering in #24489 remains readable
    while columns have usable room. Once long links or multiple prose-heavy
    columns are compressed into narrow allocations, however, the grid can
    turn words and paths into tall vertical strips that are difficult to
    scan. In those cases the content matters more than preserving the grid
    shape.
    
    ## What Changed
    
    <table>
    <tr><td>
    <p align="center"><b>
    Normal
    </b></p>
    <img width="1722" height="619" alt="CleanShot 2026-05-27 at 14 32 57"
    src="https://github.com/user-attachments/assets/d04f5fbd-6064-4acd-91bd-072d19b983df"
    />
    </td></tr>
    <tr><td>
    <p align="center"><b>
    Narrow
    </b></p>
    <img width="863" height="1013" alt="CleanShot 2026-05-27 at 14 33 12"
    src="https://github.com/user-attachments/assets/6a7d2968-0a68-48fd-ab5d-209b3dbaf03e"
    />
    </td></tr>
    <tr><td>
    <p align="center"><b>
    Very narrow
    </b></p>
    <img width="435" height="746" alt="CleanShot 2026-05-27 at 14 33 47"
    src="https://github.com/user-attachments/assets/f6a59e30-b1d2-4063-9c05-43933abc77d6"
    />
    </td></tr>
    </table>
    
    - Detect tables whose grid allocation causes systemic token
    fragmentation or starves multiple prose-heavy columns.
    - Render those tables as repeated key/value records instead of retaining
    an unreadable grid.
    - Use aligned label/value records when there is useful horizontal room,
    and switch to a stacked narrow-record layout where each label is
    followed by a full-width value when width is especially constrained.
    - Preserve the themed label color, rich inline formatting, links, and
    the existing grid presentation for tables that remain readable.
    - Add snapshot coverage for path-heavy narrow tables, prose-heavy issue
    tables, systemic compact fragmentation, and a control case that should
    continue to render as a grid.
    
    ## How to Test
    
    1. Start Codex from this branch and render a normal multi-column
    markdown table at a comfortable terminal width. Confirm it still appears
    as the styled row-separated grid from #24489.
    2. Render a table containing a long linked record identifier or
    file-like value, then narrow the terminal until the grid would split the
    value into vertical fragments. Confirm it switches to key/value records,
    with labels above values at very narrow widths.
    3. Render a table with multiple prose-heavy columns, such as an issue
    summary table with `Issue`, `Activity`, `Complexity`, and `Why start`.
    Confirm a cramped width switches to records rather than wrapping several
    columns into hard-to-read strips.
    4. Render a compact table where only one value wraps mildly. Confirm it
    stays in grid form rather than switching prematurely.
    
    ## Validation
    
    - Ran `just test -p codex-tui` while developing the fallback and
    reviewed/accepted the intended new markdown-render snapshots. The
    command still reports two unrelated existing guardian feature-flag test
    failures outside this diff.
    - Ran `just fix -p codex-tui` and `just fmt` after the Rust changes were
    complete.
    - `just argument-comment-lint` cannot reach source linting locally
    because Bazel fails while resolving LLVM sanitizer headers; touched
    positional literal callsites were inspected manually and annotated where
    needed.
  • feat(tui): render markdown tables in app style [1 of 2] (#24489)
    ## Stack
    
    - **Current: #24489 [1 of 2]** - render markdown tables in app style.
    - **Stacked follow-up: #24636 [2 of 2]** - render cramped markdown
    tables as key/value records.
    
    ## Why
    
    Markdown tables currently render as boxed terminal grids, which gives
    ordinary assistant output a heavier visual treatment than surrounding
    rich text. This row-separated layout is the best match for how the App
    renders tables, while accented headers remain distinguishable even when
    a terminal font renders bold subtly.
    
    <table>
    <tr><td>
    <p align="center">Codex CLI - Before</p>
    <img width="1722" height="742" alt="CleanShot 2026-05-25 at 18 46 17"
    src="https://github.com/user-attachments/assets/f673d92a-ebd8-46e2-b414-3d985e41b6a4"
    />
    </td></tr>
    <tr><td>
    <p align="center">Codex CLI - After</p>
    <img width="1720" height="957" alt="image"
    src="https://github.com/user-attachments/assets/36a3d331-bea1-439b-b5be-e97b0731bd6f"
    />
    </td></tr>
    <tr><td>
    <p align="center">Codex App</p>
    <img width="979" height="1293" alt="CleanShot 2026-05-25 at 18 45 04"
    src="https://github.com/user-attachments/assets/7d97cae0-9256-4f6e-a4b3-8b8f22b0d901"
    />
    </td></tr>
    </table>
    
    ## What Changed
    
    - Render markdown tables as padded, aligned rows without an enclosing
    box.
    - Style table headers with the active syntax-theme accent plus bold
    text, while keeping separators low contrast and theme-aware.
    - Use a segmented heavy header rule and thin body-row rules, preserving
    wrapping, narrow-width fallback, streaming parity, and rich-history
    rendering.
    - Update focused assertions and snapshots for the final table layout.
    
    ## How to Test
    
    1. Render a markdown table in the TUI with several rows and columns.
    2. Confirm the header uses the active theme accent, rows use
    one-character interior padding, and the table has no enclosing box.
    3. Confirm the header is followed by segmented `━` rules and multiple
    body rows are separated by muted segmented `─` rules.
    4. Render the same table while streaming and in history/raw-mode
    toggles; the final rich layout should remain stable.
    5. Render a narrow table with long content and verify wrapping or pipe
    fallback does not overflow.
    
    ## Validation
    
    - `just test -p codex-tui table`
    - `just test -p codex-tui streaming::controller::tests`
    - `just argument-comment-lint-from-source -p codex-tui -- --all-targets`
    - `just fix -p codex-tui`
    - `just fmt`
    
    `just test -p codex-tui` was also run after accepting the snapshots; it
    fails only in the unrelated existing guardian app tests
    `update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    and
    `update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`.
  • feat(tui): make turn interruption keybind configurable (#24766)
    ## Why
    
    Interrupting an active turn is currently fixed to `Esc`, which is easy
    to hit accidentally and cannot be customized through `/keymap`. This
    gives users a less accidental binding while preserving the existing
    default.
    
    ## What Changed
    
    - Adds `tui.keymap.chat.interrupt_turn` to `/keymap`, defaulting to
    `esc` and supporting remapping or unbinding.
    - Uses the configured interrupt binding for running-turn status, queued
    steer interruption, and `request_user_input`, including the visible
    hints.
    - Preserves local `Esc` behavior for popups, Vim insert mode, and
    `/agent` editing while validating conflicts with fixed/backtrack and
    request-input navigation bindings.
    - Adds behavior and snapshot coverage for remapped interruption paths.
    
    ## How to Test
    
    1. Run Codex and open `/keymap`, then set **Interrupt Turn** to `f12`.
    2. Start a turn and confirm `Esc` no longer interrupts it while `f12`
    does; the running hint should display `f12 to interrupt`.
    3. Queue a steer while a turn is running and confirm the preview
    displays `f12`; pressing it should interrupt and submit the steer
    immediately.
    4. Trigger a `request_user_input` prompt and confirm its footer uses
    `f12`; with notes open, `Esc` should still clear notes while `f12`
    interrupts the turn.
    5. Clear the Interrupt Turn binding and confirm the key-specific
    interrupt hint is removed while `Ctrl+C` remains available.
    
    Targeted validation:
    
    - `just write-config-schema`
    - `just fix -p codex-config`
    - `just fix -p codex-tui`
    - `just fmt`
    - `just argument-comment-lint-from-source -p codex-config -p codex-tui`
    - `just test -p codex-config`
    - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
    - `just test -p codex-tui keymap_setup::tests`
    - `just test -p codex-tui` (fails in two pre-existing guardian
    feature-flag tests unrelated to this diff; the intentional picker
    snapshot updates were reviewed and accepted)
  • feat(tui): add vim text object bindings (#24382)
    ## Why
    
    Vim mode currently supports some normal-mode operators and motions, but
    common text-object combinations like `ciw`, `daw`, `di(`, and
    quote/bracket variants are still missing. That makes the composer feel
    incomplete for users who expect operator + text object editing to work
    inside prompts.
    
    Closes #21383.
    
    ## What Changed
    
    - Add Vim pending-state support for operator/text-object sequences.
    - Add `c` as a normal-mode operator for text objects, so combinations
    like `ciw` delete the object and enter insert mode.
    - Support word, WORD, delimiter, and quote text objects:
      - `iw`, `aw`, `iW`, `aW`
      - `i(`, `a(`, `i)`, `a)`, `ib`, `ab`
      - `i[`, `a[`, `i]`, `a]`
      - `i{`, `a{`, `i}`, `a}`, `iB`, `aB`
      - `i"`, `a"`, `i'`, `a'`, `i\``, `a\``
    - Add configurable keymap entries and keymap picker coverage for the new
    Vim text-object context.
    - Regenerate the config schema and update keymap picker snapshots.
    
    ## How to Test
    
    Manual smoke test:
    
    1. Start Codex with Vim composer mode enabled.
    2. Type a draft such as:
       ```text
       alpha beta gamma
       call(foo[bar], {"x": "hello world"})
       say "one \"two\" three" now
       ```
    3. Put the cursor on `beta`, press `ciw`, and confirm `beta` is removed
    and the composer enters insert mode.
    4. Escape back to normal mode, put the cursor on `gamma`, press `daw`,
    and confirm `gamma` plus surrounding whitespace is removed.
    5. Put the cursor inside `foo[bar]`, press `di[`, and confirm only `bar`
    is removed.
    6. Put the cursor inside `call(...)`, press `da(`, and confirm the whole
    parenthesized section is removed.
    7. Put the cursor inside the quoted text, press `ci"`, and confirm the
    quote contents are removed and insert mode starts.
    8. Verify cancellation does not edit text: press `d` then `Esc`, and
    press `d` then `i` then `Esc`.
    
    Targeted tests:
    
    - `cargo test -p codex-tui --lib vim_`
    - `cargo nextest run -p codex-tui keymap_setup::tests`
    
    Additional local checks:
    
    - `just write-config-schema`
    - `just fmt`
    - `just fix -p codex-tui`
    - `git diff --check`
    - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
    
    Local full-suite note: `just test -p codex-tui` ran to completion. The
    keymap snapshot failures were expected and accepted. Two unrelated
    guardian feature-flag tests still fail locally:
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
    
    `just argument-comment-lint` is currently blocked locally by Bazel
    analysis before the lint runs because `compiler-rt` has an empty
    `include/sanitizer/*.h` glob in the local Bazel cache. The touched Rust
    diff was manually inspected for opaque positional literals.
  • fix(tui): complete vim word-end and line-end behavior (#24380)
    ## Why
    
    The TUI Vim composer currently diverges from normal Vim editing in two
    common workflows: pressing `e` repeatedly can remain stuck at an
    existing word end, and normal mode does not support `C` for changing
    through the end of the line. The existing `D` behavior also removes the
    newline when the cursor is already at the line boundary, which makes the
    new `C` action and existing deletion action surprising in multiline
    prompts.
    
    Closes #23926.
    Closes #24238.
    
    ## What Changed
    
    - Make normal-mode `e` advance from the current word end to the next
    word end, including for operator motions such as `de`.
    - Add configurable Vim normal-mode `change_to_line_end` behavior, bound
    to `C` by default, which deletes to the end of the current line and
    enters Insert mode.
    - Keep the newline intact when `D` or `C` is pressed at the end-of-line
    boundary.
    - Add regression coverage for repeated `e`, `de`, `C`, and the multiline
    `C`/`D` boundary behavior.
    - Regenerate the config schema and update the keymap picker snapshots
    for the new Vim action.
    
    ## How to Test
    
    1. Run Codex with Vim composer mode enabled:
       ```bash
       cd codex-rs
       cargo run --bin codex -- -c tui.vim_mode_default=true
       ```
    2. Enter `alpha beta gamma`, press `Esc`, `0`, then press `e`
    repeatedly.
    Confirm the cursor advances through the ends of `alpha`, `beta`, and
    `gamma`.
    3. Enter `hello world`, press `Esc`, `0`, `w`, then `C`.
       Confirm `world` is deleted and the composer enters Insert mode.
    4. Enter a multiline prompt with `hello` above `world`, press `Esc`,
    `k`, `$`, and then `D`.
       Confirm the newline is preserved and the two lines do not join.
    5. At the same boundary, press `C` and type `!`.
    Confirm the composer enters Insert mode and yields `hello!` above
    `world`, preserving the newline.
    
    Targeted automated verification:
    - `just fix -p codex-tui`
    - `just argument-comment-lint-from-source -p codex-tui -p codex-config`
    - `cargo insta pending-snapshots` reports no pending snapshots.
    - `just test -p codex-tui` validates the new Vim and keymap snapshot
    coverage, but the command remains red due to two reproducible unrelated
    failures in `app::tests::update_feature_flags_disabling_guardian_*`.
    
    ## Validation Note
    
    The workspace-wide `just argument-comment-lint` form is currently
    blocked during Bazel analysis by the existing LLVM `compiler-rt` missing
    `include/sanitizer/*.h` failure; package-scoped source linting for the
    changed Rust crates passed.
  • fix(tui): keep raw output above composer in zellij (#24593)
    ## Why
    
    Raw output mode intentionally sends logical source lines to the terminal
    without Codex-inserted wrapping so copied content retains its original
    line structure. In Zellij, soft-wrapped continuation rows from those raw
    lines are not confined by the inline history scroll region. When raw
    mode replays a long transcript, continuation rows can occupy the
    composer viewport and are overwritten on the following draw, leaving the
    transcript visibly truncated underneath the composer.
    
    This is specific to the combination of Zellij and raw terminal-wrapped
    history. Rich output and non-Zellij terminals should continue using the
    existing insertion behavior.
    
    Related context: #20819 introduced raw output mode, and #22214 removed
    the broad Zellij insertion workaround after the standard rich-output
    path no longer required it.
    
    | Before | After |
    |---|---|
    | <img width="1728" height="916" alt="image"
    src="https://github.com/user-attachments/assets/f85398a5-e930-46d9-bcfd-106a24c41466"
    /> | <img width="1723" height="912" alt="image"
    src="https://github.com/user-attachments/assets/5c62e16a-a6e5-4842-bcb2-eab163cda04c"
    /> |
    
    ## What Changed
    
    - Cache Zellij detection in `Tui` and select a dedicated insertion mode
    only for `HistoryLineWrapPolicy::Terminal` batches in Zellij.
    - For that guarded path, clear the existing viewport, append raw source
    lines through the terminal so its soft wrapping remains
    selection-friendly, and reserve empty viewport rows before redrawing the
    composer.
    - Add snapshot regressions for both an incremental soft-wrapped raw
    insert and an overflowing raw transcript replay that starts at the top
    of the cleared terminal.
    
    ## How to Test
    
    1. Start Codex inside Zellij with raw output enabled or toggle raw
    output after a multiline response is in history.
    2. Produce or replay output containing long logical lines, such as a
    fenced shell command with several wrapped lines.
    3. Confirm the wrapped history remains visible above the composer and
    the composer no longer overwrites the end of the response.
    4. Toggle back to rich output or run outside Zellij and confirm standard
    history rendering still behaves normally.
    
    Targeted tests run:
    
    - `just test -p codex-tui vt100_zellij_raw -- --nocapture`
    
    Additional validation notes:
    
    - `just test -p codex-tui` was attempted; the two new Zellij raw
    insertion tests passed, while two existing
    `app::tests::update_feature_flags_disabling_guardian_*` tests failed
    outside this history insertion path.
    - `just argument-comment-lint` was attempted but local Bazel analysis
    fails before reaching the changed source because the LLVM `compiler-rt`
    package is missing `include/sanitizer/*.h`. Modified literal callsites
    were inspected manually.
  • fix(tui): improve multiline markdown list readability (#24351)
    ## Why
    
    Numbered Markdown findings become hard to scan when long items visually
    run together or when wrapped explanatory paragraphs lose their list
    indentation. This is especially visible in review output: the next
    number can look attached to the previous finding, and paragraph
    continuation rows can jump back toward the left margin instead of
    staying grouped beneath their item.
    
    <table><tr><td>
    <center>Before</center>
    <img width="1718" height="836" alt="CleanShot 2026-05-24 at 14 00 49"
    src="https://github.com/user-attachments/assets/f1ee0023-50fa-4f81-a641-ae08b17b99bd"
    />
    </td></tr>
    <tr><td> 
    <center>After</center>
    <img width="1714" height="906" alt="image"
    src="https://github.com/user-attachments/assets/b123a5e0-a232-47bf-96d5-c935295f7c0a"
    />
    </td></tr>
    </table>
    
    ## What Changed
    
    - Insert a blank separator before a sibling list item when the previous
    item occupies more than one rendered line.
    - Preserve compact rendering for lists whose sibling items each render
    on one line.
    - Preserve list-body leading whitespace when transient streamed
    assistant rows require another wrapping pass for history display, so
    wrapped paragraphs stay aligned beneath their item.
    - Share the existing leading-whitespace prefix logic used by history
    insertion instead of introducing a second indentation rule.
    - Keep streamed Markdown output aligned with completed rendering and add
    snapshots for findings-style spacing and streamed paragraph indentation.
    
    ## How to Test
    
    1. Start Codex from this branch and open the recorded repro session
    `019e563f-7d58-7ff2-8ec7-828f20fa61ca`.
    2. Inspect the numbered `Findings` list whose items contain explanatory
    paragraphs.
    3. Confirm each multiline finding is separated from the next numbered
    finding by one blank line.
    4. Confirm wrapped rows of each indented paragraph remain aligned
    beneath the finding body, rather than returning to the left edge.
    5. Render a short one-line numbered or unordered list and confirm its
    items remain compact without added blank rows.
    
    Targeted tests:
    
    - `just test -p codex-tui history_cell insert_history markdown_render
    markdown_stream streaming::controller`
    - `just argument-comment-lint-from-source -p codex-tui`
    
    ## Related Work
    
    PR #24346 changes Markdown table column allocation in parallel. This PR
    is intentionally limited to list-item readability and history wrapping;
    both branches touch `codex-rs/tui/src/markdown_render.rs`, so a small
    merge conflict may need resolution depending on merge order.
  • fix(tui): improve markdown table column allocation (#24346)
    ## Why
    
    Markdown tables with a long path-heavy column could allocate almost all
    available width to that column and collapse neighboring prose columns to
    only a few characters. In rollout summaries this made `Unit` and `What
    It Adds` difficult to read, even though the long `Files` values were the
    content best suited to wrapping.
    
    The affected example also specified `Files` as right aligned in its
    markdown delimiter (`---:`). This change preserves that requested
    alignment while improving how width is distributed.
    
    | Before | After |
    |---|---|
    | <img width="1709" height="764" alt="image"
    src="https://github.com/user-attachments/assets/932ab21c-b72d-48a2-9aad-b69da87a0968"
    /> | <img width="1711" height="855" alt="image"
    src="https://github.com/user-attachments/assets/4028bd20-2228-4c2f-be8a-1866325b7f62"
    /> |
    
    
    ## What Changed
    
    - Classify table columns as narrative, token-heavy, or compact during
    width allocation.
    - Shrink token-heavy path and URL columns before shrinking narrative
    prose, while preserving compact counts and short labels longest.
    - Use readable soft floors for narrative and token-heavy content before
    falling back to tighter layouts.
    - Add snapshot coverage for a rollout-shaped table containing
    right-aligned file paths and prose columns.
    
    ## How to Test
    
    1. Render a markdown table with `Unit`, right-aligned `Files`, `Adds`,
    `Removes`, and `What It Adds` columns at a constrained terminal width.
    2. Put long repository paths in `Files` and sentence-length content in
    `Unit` and `What It Adds`.
    3. Confirm that `Files` remains right aligned but wraps before the
    narrative columns become unreadable.
    4. Confirm that the compact numeric columns remain easy to scan.
    
    Targeted tests:
    - `just test -p codex-tui markdown_render`
    
    Validation note: `just test -p codex-tui` was also attempted and reached
    two existing unrelated failures in
    `app::tests::update_feature_flags_disabling_guardian_*`; the markdown
    rendering regression test passes in the targeted run.
  • TUI: split history cells into focused modules (#22704)
    ## Why
    
    `codex-rs/tui/src/history_cell.rs` had become the dumping ground for
    transcript rendering: the shared trait, common helpers, and the concrete
    cells for messages, plans, MCP/search, notices, patches, approvals,
    session chrome, and separators all lived together. That made small
    transcript changes require reopening a very large file and made
    ownership less obvious.
    
    ## What changed
    
    - Replaced the monolithic `history_cell.rs` with a `history_cell/`
    module tree organized by concern.
    - Kept the existing `crate::history_cell::*` surface stable through
    re-exports in `history_cell/mod.rs`.
    - Moved the existing render coverage into `history_cell/tests.rs`.
    
    ## Reviewer notes
    
    - This PR is intentionally mechanical in mature — existing code and
    tests moving into files that match their concern.
    - The snapshot files under `codex-rs/tui/src/history_cell/snapshots/`
    moved with the extracted test module. `insta` resolves these unnamed
    snapshots relative to the source file that declares them, so this is
    path churn only; snapshot contents were not updated.
    - The small non-mechanical seam edits are limited to split fallout:
    sibling-module visibility for shared cell containers, moving
    approval-specific exec-snippet helpers beside approvals, fixing the
    separator module path, and keeping a couple of existing test helpers
    reachable after extraction.
  • Prevent Esc from dismissing or rewinding /side (#22710)
    Addresses #22599
    
    ## Why
    `/side` currently lets `Esc` return to the parent thread. Multiple users
    reported that this collides with queued-steer UI that also advertises
    `Esc`, so a timing-sensitive keypress can dismiss an ephemeral side chat
    instead of sending the queued prompt.
    
    After removing that dismissal shortcut, the same `Esc` path could fall
    through to main-thread backtrack/edit-previous handling, which is not
    valid for ephemeral side conversations. This keeps `/side` out of both
    global `Esc` behaviors.
    
    ## What changed
    - Remove `Esc` from the `/side` return shortcut matcher while keeping
    the existing `Ctrl+C` and `Ctrl+D` behavior.
    - Update side-conversation hints and blocked-command copy to advertise
    `Ctrl+C` as the return shortcut.
    - Rename the reserved `Esc` keymap label to describe backtracking only.
    - Block backtrack/edit-previous handling while a side conversation is
    active and report `Editing previous prompts is unavailable in side
    conversations.` when that path would have fired.
    - Keep composer-owned `Esc` behavior, such as Vim insert-mode escape,
    routed locally.
    - Refresh focused shortcut assertions and TUI snapshots for the updated
    footer and new side-conversation error message.
    
    ## Verification
    Manually tested `/side` use cases and `Esc`, `Ctrl+C`, `Ctrl+D`.
  • feat(tui): standardize picker navigation keys (#22347)
    ## Why
    
    Picker-style UI in the TUI has accumulated a mix of hardcoded navigation
    keys. Some lists supported page movement, some did not; some accepted
    Vim-like keys, while others only accepted arrows; and tabbed or
    horizontally adjustable pickers had no shared keymap action for
    left/right movement.
    
    This PR makes picker/list navigation consistent and configurable so
    users can rely on the same defaults across the TUI.
    
    ## What Changed
    
    - Adds shared list keymap actions for:
      - vertical movement: `move_up`, `move_down`
      - horizontal movement: `move_left`, `move_right`
      - paging and jumps: `page_up`, `page_down`, `jump_top`, `jump_bottom`
    - Adds defaults:
    - Up/down: arrows, `Ctrl+P/N`, `Ctrl+K/J`, and plain `k/j` where text
    input is not active
      - Page up/down: `PageUp/PageDown` and `Ctrl+B/F`
      - First/last: `Home/End`
      - Left/right: `Left/Right` and `Ctrl+H/L`
    - Wires the shared list keymap through picker and list surfaces
    including session resume, multi-select, tabbed selection lists,
    settings-style lists, app-link selection, MCP elicitation,
    request-user-input, and the OSS selection wizard.
    - Keeps search behavior intact by reserving printable characters for
    query text in searchable pickers.
    - Updates keymap setup actions, config schema, snapshots, and focused
    coverage for the new list actions.
    
    ## How to Test
    
    1. Start Codex from this branch and open the session picker, for example
    with an existing session history.
    2. In the session list, verify that `Ctrl+J/K` moves the selection
    down/up.
    3. Verify that `Ctrl+F/B` pages down/up and `Home/End` jumps to the
    first/last visible session.
    4. Type printable search text such as `j` or `k` and confirm it updates
    the query instead of navigating.
    5. Focus a picker control that changes values horizontally, such as a
    session picker toolbar control, and verify `Ctrl+H/L` changes the
    focused value like left/right arrows.
    
    Targeted tests run:
    
    - `cargo test -p codex-tui keymap::tests::`
    - `cargo test -p codex-tui keymap_setup::tests::`
    - `cargo test -p codex-tui horizontal_list_keys`
    - `cargo test -p codex-tui page_and_jump_navigation_use_list_keymap`
    - `cargo test -p codex-tui ctrl_h_l_move_provider_selection`
    - `cargo test -p codex-tui scroll_state::tests`
    - `cargo test -p codex-tui
    switching_tabs_changes_visible_items_and_clears_search`
    - `cargo test -p codex-tui toggle_sort_key_reloads_with_new_sort`
    
    Also ran `just write-config-schema`, `just fmt`, `just fix -p
    codex-tui`, `just argument-comment-lint`, and `git diff --check`.
    
    Note: `cargo test -p codex-tui` was attempted and still aborts in the
    pre-existing
    `tests::fork_last_filters_latest_session_by_cwd_unless_show_all` stack
    overflow, which is unrelated to this branch.
  • add --dangerously-bypass-hook-trust CLI flag (#21768)
    # Why
    
    Hook trust happens through the TUI in `/hooks` so it can block
    non-interactive use cases. This flag will allow users that are using
    codex headlessly to bypass hooks when they want to.
    
    # What
    
    This adds one invocation-scoped escape hatch.
    
    - the CLI flag sets a runtime-only `bypass_hook_trust` override; there
    is no durable `config.toml` setting
    - hook discovery still respects normal enablement, so explicitly
    disabled hooks remain disabled
    - we show a `--dangerously-bypass-hook-trust is enabled. Enabled hooks
    may run without review for this invocation.` message on startup so
    accidental use is visible in both interactive and exec flows
    
    This keeps “enabled” and “trusted” as separate concepts in the normal
    path, while giving CI/E2E callers a stable way to opt into the
    exceptional path when they already control the hook set.
  • feat(tui): render responsive Markdown tables in TUI (#22052)
    ## Why
    
    The TUI currently treats Markdown tables as ordinary wrapped text, which
    makes table-heavy responses hard to read and brittle across narrow panes
    and terminal resizes.
    
    This change teaches the TUI to render Markdown tables responsively while
    preserving the raw Markdown source needed to re-render streamed and
    finalized transcript content after width changes. The goal is to keep
    tables legible during streaming, after resize, and once a turn has
    finished, without corrupting scrollback ordering.
    
    ## What Changed
    
    - add table detection and responsive table rendering in the Markdown
    renderer
    - render standard tables with Unicode box-drawing borders when the pane
    is wide enough
    - add a vertical readability fallback for constrained or dense tables so
    narrow panes still show each row clearly
    - keep links and `<br>` content inside table cells instead of leaking
    text outside the table
    - avoid table normalization inside fenced or indented code blocks
    - preserve raw streamed Markdown source and keep the active table as a
    mutable tail until finalization
    - consolidate finalized streamed content into source-backed transcript
    cells so post-resize re-rendering stays correct
    - add snapshot and targeted streaming/resize regression coverage for the
    new table behavior
    
    ## How to Test
    
    1. Start Codex TUI from this branch.
    2. Paste this exact prompt:
    `This is a session to test codex, no need to do any thinking, just end
    different markdown tables, with columns exploring different markdown
    contents, like links, bold italic, code, etc. Make them different sizes,
    some 30+ rows, some not and intertwine them with some paragraphs with
    complex formatting as well.`
    3. Confirm the response includes several Markdown tables mixed with
    richly formatted paragraphs.
    4. Confirm wide-enough tables render with box-drawing borders instead of
    plain wrapped pipe text.
    5. Resize the terminal narrower while the answer is still streaming and
    confirm the in-progress table stays coherent instead of duplicating
    headers or leaving broken scrollback behind.
    6. Resize again after the turn finishes and confirm the finalized
    transcript re-renders cleanly at the new width.
    7. In a narrow pane, verify dense tables fall back to the vertical
    per-row layout instead of producing unreadable wrapped columns.
    8. Also verify pipe-heavy fenced code blocks still render as code, not
    as tables.
    
    Targeted tests:
    - `cargo test -p codex-tui table_readability_fallback --no-fail-fast`
    - `cargo test -p codex-tui markdown_render --no-fail-fast`
    - `cargo test -p codex-tui streaming::controller --no-fail-fast`
    - `cargo test -p codex-tui table_resize_lifecycle --no-fail-fast`
    
    ## Docs
    
    No developer docs update appears necessary.
  • Improve hooks trust flow in TUI (#21755)
    # Why
    Hooks that need trust review were easy to miss, and the existing TUI
    flow made users discover `/hooks` manually before they could decide
    whether to inspect or trust them.
    
    # What
    - add a startup review prompt for new or changed hooks before normal
    composer use
    - add a top-level `t` shortcut in `/hooks` to trust every review-needed
    hook at once
    - make pending-review rows and helper copy use warning styling
    
    ## TUI
    
    ### Startup review interstitial
    
    ```text
    Hooks need review
    2 hooks are new or changed.
    Hooks can run outside the sandbox after you trust them.
    
    › 1. Review hooks
      2. Trust all and continue
      3. Continue without trusting (hooks won't run)
    ```
    
    ### Top-level `/hooks` page when review is needed
    
    ```text
    Hooks
    Lifecycle hooks from config and enabled plugins.
    
    ⚠ 1 hook needs review before it can run.
    
    Event                 Installed   Active   Review   Description
    PreToolUse            1           0        1        Before a tool executes
    ...
    
    Press t to trust all; enter to review hooks; esc to close
    ```
  • fix(tui): preserve wrapped prose beside URLs (#21760)
    ## Why
    
    Mixed prose lines that contained URLs started taking the URL-preserving
    wrapping path, but that path could split ordinary words mid-token. A
    follow-up issue remained in scrollback insertion: when already-rendered
    indented rows were wrapped again, continuation rows could lose their
    margin and fall back to terminal hard wrapping. Together those bugs made
    normal Markdown output look broken around links, lists, blockquotes, and
    indented content.
    
    Separately, the local argument-comment lint wrappers failed under
    environments that set `PYTHONSAFEPATH=1`, because Python no longer adds
    the script directory to `sys.path` automatically. That prevented the
    lint from reaching Rust callsites at all.
    
    <img width="1778" height="1558" alt="CleanShot 2026-05-09 at 11 51 38"
    src="https://github.com/user-attachments/assets/9274d150-1757-4f1a-89ac-5bdc9997d8cb"
    />
    
    ## What Changed
    
    - Preserve URL tokens without turning every neighboring prose word into
    a character-level split point.
    - Add a mixed URL/prose wrapper that keeps ordinary words whole,
    preserves leading whitespace, and re-splits long non-URL tokens against
    the actual width available on continuation rows.
    - Reuse a rendered history row's leading whitespace as the continuation
    indent when scrollback insertion has to pre-wrap it again.
    - Add regression coverage for markdown wrapping, history-cell rendering,
    scrollback continuation margins, leading-indent width accounting, and
    continuation-row re-splitting.
    - Make both argument-comment lint entrypoints explicitly add their own
    directory to `sys.path`, so sibling imports still work when
    `PYTHONSAFEPATH=1`.
    
    ## How to Test
    
    1. Start Codex and render a long Markdown response that mixes prose with
    inline links, blockquotes, lists, and indented code-like text.
    2. Confirm that ordinary words next to links stay whole instead of
    breaking mid-word.
    3. Resize or replay the transcript and confirm wrapped continuation rows
    keep their expected left margin for blockquotes, lists, and indented
    content.
    4. Run the source argument-comment lint from a shell with
    `PYTHONSAFEPATH=1` and confirm it starts normally instead of failing to
    import `wrapper_common`.
    
    Targeted tests:
    - `cargo test -p codex-tui mixed_line --lib`
    - `cargo test -p codex-tui preserves_prefix_on_wrapped_rows --lib`
    - `cargo test -p codex-tui
    agent_markdown_cell_does_not_split_words_after_inline_markdown --lib`
    - `cargo test -p codex-tui
    mixed_url_markdown_wraps_prose_without_splitting_words_snapshot --lib`
    - `python3 tools/argument-comment-lint/test_wrapper_common.py`
    - `just argument-comment-lint-from-source -p codex-tui -- --lib`
    
    Notes:
    - `cargo test -p codex-tui` currently reaches the new tests
    successfully, then still aborts in the pre-existing
    `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`
    stack-overflow failure.
  • feat(tui): redesign session picker (#20065)
    ## Why
    
    The resume/fork picker is becoming the main way users recover previous
    work, but the old fixed table made sessions hard to scan once thread
    names, branches, working directories, and timestamps all mattered. This
    redesign makes the picker denser by default, easier to search, and safer
    to inspect before resuming or forking.
    
    <table>
    <tr>
    <td>
    <img width="1660" height="1103" alt="CleanShot 2026-05-03 at 12 34 10"
    src="https://github.com/user-attachments/assets/313ede1d-1da4-4863-acd2-56b3e27e9703"
    />
    </td>
    <td>
    <img width="1662" height="1100" alt="CleanShot 2026-05-03 at 12 34 15"
    src="https://github.com/user-attachments/assets/cfde7d5c-bab0-4994-a807-254e53f344ea"
    />
    </td>
    </tr>
    <tr>
    <td>
    <img width="1664" height="1107" alt="CleanShot 2026-05-03 at 12 39 22"
    src="https://github.com/user-attachments/assets/e1ee58ca-4dc5-4a35-ae0f-47562da3974c"
    />
    </td>
    <td>
    <img width="1662" height="1100" alt="CleanShot 2026-05-03 at 12 35 09"
    src="https://github.com/user-attachments/assets/9c888072-eedf-4f45-985c-0c14df28bcc7"
    />
    </td>
    </tr>
    </table>
    
    ## What Changed
    
    - Replaces the old session table with responsive session rows that
    prioritize the session name or preview, then show timestamp, cwd, and
    branch metadata.
    - Makes dense view the default while keeping comfortable view available
    through `Ctrl+O`.
    - Persists the picker view preference in `[tui].session_picker_view`,
    including active profile-scoped config.
    - Adds sort/filter controls for updated time, created time, cwd, and all
    sessions.
    - Expands search matching across session name, preview, thread id,
    branch, and cwd.
    - Makes `Esc` safer in search mode: it clears an active query before
    starting a new session.
    - Adds lazy transcript inspection:
      - `Space` expands recent transcript context inline.
      - `Ctrl+T` opens a transcript overlay.
      - raw reasoning visibility follows `show_raw_agent_reasoning`.
    - Keeps remote cwd filtering server-side for remote app-server sessions
    so local path normalization does not incorrectly hide remote results.
    - Updates snapshots and config schema for the new picker states and
    config option.
    
    ## How to Test
    
    1. Start Codex in a repo with several saved sessions.
    2. Press `Ctrl+R` / resume picker entry point.
    3. Confirm the picker opens in dense mode and shows session name or
    preview, timestamp, cwd, and branch metadata.
    4. Press `Ctrl+O` and confirm it switches between dense and comfortable
    views.
    5. Restart Codex and confirm the selected view persists.
    6. Type a query that matches a branch, cwd, thread id, or session name;
    confirm matching sessions appear.
    7. Press `Esc` while the query is non-empty and confirm it clears search
    instead of starting a new session.
    8. Select a session and press `Space`; confirm recent transcript context
    expands inline.
    9. Press `Ctrl+T`; confirm the transcript overlay opens and respects
    raw-reasoning visibility settings.
    
    Targeted tests:
    - `cargo test -p codex-tui resume_picker --no-fail-fast`
    - `cargo test -p codex-core
    runtime_config_resolves_session_picker_view_default_and_override`
    - `cargo test -p codex-core profile_tui_rejects_unsupported_settings`
    - `cargo check -p codex-thread-manager-sample`
    - `cargo insta pending-snapshots`
  • hook trust metadata and enforcement (#20321)
    # Why
    
    We want shared hook trust that both the app and the TUI can build on,
    but the metadata is only useful if runtime behavior agrees with it. This
    PR adds a single backend trust model for hooks so unmanaged hooks cannot
    run until the current definition has been reviewed, while managed hooks
    remain runnable and non-configurable.
    
    # What
    
    - persist `trusted_hash` alongside hook state in `config.toml`
    - expose `currentHash` and derived `trustStatus` through `hooks/list`
    - derive trust from normalized hook definitions so equivalent hooks from
    `config.toml` and `hooks.json` share the same trust identity
    - gate unmanaged hooks on trust before they enter the runnable handler
    set
    
    # Reviewer Notes
    
    - key file to review is `codex-rs/hooks/src/engine/discovery.rs`
    - the only **core** change is schema related
  • 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`
  • feat(tui): improve TUI keymap coverage (#20798)
    ## Summary
    - normalize terminal-emitted C0 control characters through configurable
    editor keymaps, covering raw control-key fallbacks like
    Shift+Enter-as-LF in terminals from #20555 and #20898, plus part of the
    modified-Enter behavior in #20580
    - add default-unbound keymap actions for toggling Fast mode and killing
    the current composer line, giving #20698 users a configurable zsh-style
    Ctrl+U option without changing the existing default Ctrl+U behavior
    - wire the new actions through gated /keymap picker entries, schema
    generation, and snapshot coverage
    
    Fixes #20555.
    Fixes #20898.
    
    ## Testing
    - just write-config-schema
    - just fmt
    - cargo test -p codex-config
    - cargo test -p codex-tui keymap::tests
    - cargo test -p codex-tui bottom_pane::textarea::tests
    - cargo test -p codex-tui keymap_setup::tests
    - cargo insta pending-snapshots
    - just fix -p codex-tui
    - git diff --check
    - just argument-comment-lint
  • feat(tui): add keymap debug inspector (#20794)
    ## Why
    
    We constantly get bug reports about keys not being recognized by Codex
    when the terminal is not handling the key press. Running `/keymap debug`
    or `/keymap` and going to the Debug tab, we can allow the user to either
    understand that the key being pressed is not being recognized or to
    check what it's being recognized as and report or reassign that key.
    
    | Menu | Inspector | Hint |
    |---|---|---|
    | <img width="1369" height="796" alt="CleanShot 2026-05-02 at 12 57 12"
    src="https://github.com/user-attachments/assets/512b6faa-344e-4aee-9c00-b4bdc633a662"
    /> | <img width="1261" height="754" alt="CleanShot 2026-05-02 at 12 56
    36"
    src="https://github.com/user-attachments/assets/a6ddae7d-e174-4ee4-893f-e6bec4fff4ab"
    /> | <img width="1369" height="796" alt="CleanShot 2026-05-02 at 12 57
    30"
    src="https://github.com/user-attachments/assets/db507784-f40a-4cff-ac23-a61d9703769b"
    /> |
    ## Summary
    - add a Debug tab to `/keymap` and support `/keymap debug` for direct
    access
    - show what key Codex receives, the config key representation, raw event
    details, and matching actions
    - add a progressive missing-key hint that escalates after a few seconds
    with no detected keypress
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-tui keymap_setup::tests::debug_view`
    - `cargo test -p codex-tui keymap_setup::tests`
    - `cargo test -p codex-tui slash_keymap`
    - `cargo test -p codex-tui` (unit tests passed; integration test
    `suite::model_availability_nux::resume_startup_does_not_consume_model_availability_nux_count`
    failed locally by itself with `codex resume` exiting 1 and terminal
    probe escape output)
    - `just fix -p codex-tui`
    - `just argument-comment-lint`
    - `cargo insta pending-snapshots`
    - `git diff --check`
  • fix(tui): restore alt-enter newline alias (#20535)
    Fixes https://github.com/openai/codex/issues/20501
    
    ## Summary
    - add Alt+Enter to the built-in editor newline aliases
    - update keymap tests that used Alt+Enter as a custom submit binding now
    that it conflicts with newline
    - refresh the keymap action-menu snapshot fixture
    
    ## Test Plan
    - `just fmt`
    - `cargo test -p codex-tui keymap::tests`
    - `cargo test -p codex-tui bottom_pane::textarea::tests`
    - `cargo test -p codex-tui keymap_setup::tests`
    - `cargo test -p codex-tui`
    - `cargo insta pending-snapshots`
    - `git diff --check`
    - `just argument-comment-lint`
  • Enforce animations = false for screen readers (#20564)
    ## Why
    
    Issue #20489 calls out that animated TUI affordances can be noisy for
    screen-reader users. Codex already has `tui.animations = false` as a
    reduced-motion setting, but some live activity rows render spinner-style
    prefixes in that mode. These were relatively recent regressions.
    
    We have also regressed this pattern more than once by adding new
    spinner/shimmer callsites that do not think through the reduced-motion
    path, so this PR adds a small guardrail while fixing the current
    surfaces.
    
    ## What changed
    
    - Omit the live status-row spinner when animations are disabled, so the
    row starts with stable text like `Working (...)`.
    - Render running hook headers without the spinner prefix when animations
    are disabled, while preserving shimmer/spinner behavior when animations
    are enabled.
    - Centralize TUI activity indicators in `tui/src/motion.rs`, with
    explicit reduced-motion choices for hidden prefixes, static bullets, and
    plain shimmer-text fallbacks.
    - Route existing spinner/shimmer callsites through the central motion
    helper, including exec rows, MCP/web-search/loading rows, hook rows,
    plugin loading, and onboarding loading text.
    - Add a source-scan regression test that rejects direct `spinner(...)`
    or `shimmer_spans(...)` usage outside the central module and primitive
    definition.
    - Add focused coverage that reduced-motion active exec rows are stable,
    status rows start without a spinner, running hooks omit the spinner, and
    MCP inventory loading stays stable.
    - Update the one affected status-indicator snapshot; the existing detail
    tree prefix remains unchanged.
    
    ## Verification
    
    - `cargo test -p codex-tui`
  • 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.
  • feat(tui): add configurable keymap support (#18593)
    ## Why
    
    The TUI currently handles keyboard shortcuts as hard-coded event matches
    spread across app, composer, pager, list, approval, and navigation code.
    That makes shortcuts hard to customize, makes displayed hints easy to
    drift from actual behavior, and makes future keymap work riskier because
    there is no central action inventory.
    
    This PR adds the foundation for configurable, action-based keymaps
    without adding the interactive remapping UI yet. Onboarding
    intentionally stays on fixed startup shortcuts because users cannot
    reasonably configure keymaps before completing onboarding.
    
    This is PR1 in the keymap stack:
    
    - PR1: #18593: configurable keymap foundation
    - PR2: #18594: `/keymap` picker and guided remapping UI
    - PR3: #18595: Vim composer mode and the remap option
    
    ## Design Notes
    
    The new model resolves named actions into concrete runtime bindings once
    from config, then passes those bindings to the UI surfaces that handle
    input or render shortcut hints.
    
    The main concepts are:
    
    - **Context**: a scope where an action is active, such as `global`,
    `chat`, `composer`, `editor`, `pager`, `list`, or `approval`.
    - **Action**: a named operation inside a context, such as
    `global.open_transcript`, `composer.submit`, or `pager.close`.
    - **Binding**: one or more single-key shortcuts assigned to an action,
    written as config strings such as `ctrl-t`, `alt-backspace`, or
    `page-down`. Multi-step sequences such as `ctrl-x ctrl-s`, `g g`, or
    leader-key flows are not part of this PR.
    - **Resolution order**: context-specific config wins first, supported
    global fallbacks come next, and built-in defaults fill in anything
    unset.
    - **Explicit unbinding**: an empty array removes an action binding in
    that scope and does not fall through to a fallback binding.
    - **Conflict validation**: a resolved keymap rejects duplicate active
    bindings inside the same scope so one keypress cannot dispatch two
    actions.
    
    ## What Changed
    
    - Added `TuiKeymap` config support under `[tui.keymap]`, including typed
    contexts/actions, key alias normalization, generated schema coverage,
    and user-facing config errors.
    - Added `RuntimeKeymap` resolution in `codex-rs/tui/src/keymap.rs`,
    including fallback precedence, built-in defaults, explicit unbinding,
    and per-context conflict validation.
    - Rewired existing TUI handlers to consume resolved keymap actions
    instead of directly matching hard-coded keys in each component.
    - Updated key hint rendering and footer/pager/list surfaces so displayed
    shortcuts follow the resolved keymap.
    - Kept onboarding shortcuts fixed in
    `codex-rs/tui/src/onboarding/keys.rs` instead of exposing them through
    `[tui.keymap]`.
    
    ## Validation
    
    The branch includes focused coverage for config parsing, key
    normalization, runtime fallback resolution, explicit unbinding,
    duplicate-key conflict validation, default keymap consistency,
    onboarding startup key behavior, and UI hint snapshots affected by
    resolved key bindings.
  • Preserve TUI markdown list spacing after code blocks (#19706)
    ## Why
    
    Fixes #19702.
    
    The TUI markdown renderer could visually attach the next list marker to
    a fenced code block inside the previous list item, even when the source
    markdown included a blank line before the next item. That made
    block-heavy loose lists harder to read, while the desired behavior is
    still to keep simple lists compact.
    
    ## What changed
    
    - Track whether the current rendered list item contains a code block.
    - Preserve one blank separator before the following list marker only
    when the previous item contained a code block.
    - Add regression coverage for both paths: code-block list items keep the
    separator, and simple loose list items stay compact.
    
    ## Verification
    
    - `cargo test -p codex-tui markdown_render`
    
    I also manually verified that the bug exists before and is fixed after.
    
    ## Before
    <img width="437" height="240" alt="Screenshot 2026-04-26 at 1 19 01 PM"
    src="https://github.com/user-attachments/assets/3bc9d64d-2dba-40d9-9d6b-a1d0b3c0f728"
    />
    
    ## After
    <img width="410" height="269" alt="Screenshot 2026-04-26 at 1 18 54 PM"
    src="https://github.com/user-attachments/assets/19c15bee-da32-455e-a7cb-e05eb85f4ea0"
    />
  • Hide rewind preview when no user message exists (#19510)
    ## Why
    
    Fixes #19508.
    
    In a fresh TUI session, pressing `Esc` twice entered the rewind
    transcript overlay even though there was no user message to rewind to.
    That produced an empty header-only transcript view and exposed a rewind
    flow that could not select a valid target.
    
    ## What changed
    
    The backtrack flow now checks whether a user-message rewind target
    exists before opening the transcript preview. If no target exists, Codex
    stays in the main TUI and shows `No previous message to edit.` instead
    of opening an empty overlay.
    
    The same guard applies when starting rewind preview from the transcript
    overlay, and the first `Esc` no longer advertises the “edit previous
    message” hint when there is no previous message available.
    
    Snapshot coverage was added for the unavailable rewind info message,
    along with a small target-detection test.
  • Add safety check notification and error handling (#19055)
    Adds a new app-server notification that fires when a user account has
    been flagged for potential safety reasons.
  • fix: windows snapshot for external_agent_config_migration::tests::prompt_snapshot did not match windows output (#18915)
    Fix a snapshot test that is failing on Windows, but is currently missed
    by Bazel due to https://github.com/openai/codex/pull/18913. We see this
    failing on Cargo builds on Windows, though.
    
    This Bazel vs. Cargo inconsistency explains why
    https://github.com/openai/codex/pull/18768 did not fix the Cargo Windows
    build.
  • [codex] Tighten external migration prompt tests (#18768)
    ## Summary
    - tighten the external migration prompt snapshot around stable synthetic
    fixture text
    - add focused display_description tests for relative path rewriting and
    plugin summaries
    - split the path-format assertions into smaller, easier-to-read unit
    tests
    
    ## Why
    The previous prompt snapshot was coupled to path text that came from
    detected migration items, which made it noisier and more brittle than
    necessary. This change keeps the snapshot focused on stable UI structure
    and moves dynamic path formatting checks into targeted unit tests.
    
    ## Validation
    - cargo test -p codex-tui external_agent_config_migration::tests::
    - cargo test -p codex-tui
    external_agent_config_migration::tests::display_description_
    - just fmt
    
    ## Notes
    Per the repo instructions, I did not rerun tests after the final `just
    fmt` pass.
  • Fix stale model test fixtures (#18719)
    Fixes stale test fixtures left after the active bundled model catalog
    updates in #18586 and #18388. Those changes made `gpt-5.4` the current
    default and removed several older hardcoded slugs, which left Windows
    Bazel shards failing TUI and config tests.
    
    What changed:
    - Refresh TUI model migration, availability NUX, plan-mode, status, and
    snapshot fixtures to use active bundled model slugs.
    - Update the config edit test expectation for the TOML-quoted
    `"gpt-5.2"` migration key.
    - Move the model catalog tests into
    `codex-rs/tui/src/app/tests/model_catalog.rs` so touching them does not
    trip the blob-size policy for `app.rs`.
    
    Verification:
    - CI Bazel/lint checks are expected to cover the affected test shards.
  • Add verbose diagnostics for /mcp (#18610)
    Fixes #18539.
    
    ## Summary
    The recent `/mcp` performance work kept the default command fast by
    avoiding resource and resource-template inventory probes, but it also
    removed useful diagnostics for users trying to confirm MCP server state.
    
    This keeps bare `/mcp` on the fast tools/auth path and adds `/mcp
    verbose` for the slower diagnostic view. Verbose mode requests full MCP
    server status from the app-server and restores status, resources, and
    resource templates in the TUI output.
    
    ## Testing
    In addition to running automation, I manually tested the feature to
    confirm that it works.
  • Add /side conversations (#18190)
    The TUI supports long-running turns and agent threads, but quick side
    questions have required interrupting the main flow or manually
    forking/navigating threads. This PR adds a guarded `/side` flow so users
    can ask brief side-conversation questions in an ephemeral fork while
    keeping the primary thread focused. This also helps address the feature
    request in #18125.
    
    The implementation creates one side conversation at a time, lets `/side`
    open either an empty side thread or immediately submit `/side
    <question>`, and returns to the parent with Esc or Ctrl+C. Side
    conversations get hidden developer guardrails that treat inherited
    history as reference-only and steer the model away from workspace
    mutations unless explicitly requested in the side conversation.
    
    The TUI hides most slash commands while side mode is active, leaving
    only `/copy`, `/diff`, `/mention`, and `/status` available there.
  • [TUI] add external config migration prompt when start TUI (#17891)
    - add a TUI startup migration prompt for external agent config
    - support migrating external configs including config, skills, AGENTS.md
    and plugins
    - gate the prompt behind features.external_migrate (default false)
    
    <img width="1037" height="480" alt="Screenshot 2026-04-14 at 9 29 14 PM"
    src="https://github.com/user-attachments/assets/6060849b-03cb-429a-9c13-c7bb46ad2e65"
    />
    <img width="713" height="183" alt="Screenshot 2026-04-14 at 9 29 26 PM"
    src="https://github.com/user-attachments/assets/d13f177e-d4c4-479c-8736-ef29636081e1"
    />
    
    ---------
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • Display YOLO mode permissions if set when launching TUI (#17877)
    - When launching the TUI client, if YOLO mode is enabled, display this
    in the header.
    - Eligibility is determined by `approval_policy = "never"` and
    `sandbox_mode = "danger-full-access"`
    
    <img width="886" height="230" alt="image"
    src="https://github.com/user-attachments/assets/d7064778-e32c-4123-8e44-ca0c9016ab09"
    />
  • [codex] Show ctrl + t hint on truncated exec output in TUI (#17076)
    ## What
    
    Show an inline `ctrl + t to view transcript` hint when exec output is
    truncated in the main TUI chat view.
    
    ## Why
    
    Today, truncated exec output shows `… +N lines`, but it does not tell
    users that the full content is already available through the existing
    transcript overlay. That makes hidden output feel lost instead of
    discoverable.
    
    This change closes that discoverability gap without introducing a new
    interaction model.
    
    Fixes: CLI-5740
    
    ## How
    
    - added an output-specific truncation hint in `ExecCell` rendering
    - applied that hint in both exec-output truncation paths:
      - logical head/tail truncation before wrapping
      - row-budget truncation after wrapping
    - preserved the existing row-budget behavior on narrow terminals by
    reserving space for the longer hint line
    - updated the relevant snapshot and added targeted regression coverage
    
    ## Intentional design decisions
    
    - **Aligned shortcut styling with the visible footer UI**  
    The inline hint uses `ctrl + t`, not `Ctrl+T`, to match the TUI’s
    rendered key-hint style.
    
    - **Kept the noun `transcript`**  
    The product already exposes this flow as the transcript overlay, so the
    hint points at the existing concept instead of inventing a new label.
    
    - **Preserved narrow-terminal behavior**  
    The longer hint text is accounted for in the row-budget truncation path
    so the visible output still respects the existing viewport cap.
    
    - **Did not add the hint to long command truncation**  
    This PR only changes hidden **output** truncation. Long command
    truncation still uses the plain ellipsis form because `ctrl + t` is not
    the same kind of “show hidden output” escape hatch there.
    
    - **Did not widen scope to other truncation surfaces**  
    This does not change MCP/tool-call truncation in `history_cell.rs`, and
    it does not change transcript-overlay behavior itself.
    
    ## Validation
    
    ### Automated
    - `just fmt`
    - `cargo test -p codex-tui`
    
    ### Manual
    - ran `just tui-with-exec-server`
    - executed `!seq 1 200`
    - confirmed the main view showed the new `ctrl + t to view transcript`
    truncation hint
    - pressed `ctrl + t` and confirmed the transcript overlay still exposed
    the full output
    - closed the overlay and returned to the main view
    
    ## Visual proof
    
    Screenshot/video attached in the PR UI showing:
    - the truncated exec output row with the new hint
    - the transcript overlay after `ctrl + t`
  • Use model metadata for Fast Mode status (#16949)
    Fast Mode status was still tied to one model name in the TUI and
    model-list plumbing. This changes the model metadata shape so a model
    can advertise additional speed tiers, carries that field through the
    app-server model list, and uses it to decide when to show Fast Mode
    status.
    
    For people using Codex, the behavior is intended to stay the same for
    existing models. Fast Mode still requires the existing signed-in /
    feature-gated path; the difference is that the UI can now recognize any
    model the model list marks as Fast-capable, instead of requiring a new
    client-side slug check.
  • Speed up /mcp inventory listing (#16831)
    Addresses #16244
    
    This was a performance regression introduced when we moved the TUI on
    top of the app server API.
    
    Problem: `/mcp` rebuilt a full MCP inventory through
    `mcpServerStatus/list`, including resources and resource templates that
    made the TUI wait on slow inventory probes.
    
    Solution: add a lightweight `detail` mode to `mcpServerStatus/list`,
    have `/mcp` request tools-and-auth only, and cover the fast path with
    app-server and TUI tests.
    
    Testing: Confirmed slow (multi-second) response prior to change and
    immediate response after change.
    
    I considered two options:
    1. Change the existing `mcpServerStatus/list` API to accept an optional
    "details" parameter so callers can request only a subset of the
    information.
    2. Add a separate `mcpServer/list` API that returns only the servers,
    tools, and auth but omits the resources.
    
    I chose option 1, but option 2 is also a reasonable approach.
  • Fix resume picker timestamp labels and stability (#16822)
    Problem: The resume picker used awkward "Created at" and "Updated at"
    headers, and its relative timestamps changed while navigating because
    they were recomputed on each redraw.
    
    Solution: Rename the headers to "Created" and "Updated", and anchor
    relative timestamp formatting to the picker load time so the displayed
    ages stay stable while browsing.