feat(tui): add terminal title support to tui app server#15860
Merged
etraut-openai merged 4 commits intoopenai:mainfrom Mar 26, 2026
Merged
feat(tui): add terminal title support to tui app server#15860etraut-openai merged 4 commits intoopenai:mainfrom
etraut-openai merged 4 commits intoopenai:mainfrom
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds configurable terminal title support to the tui_app_server, including a /title setup UI, persistence, and runtime OSC title updates driven by shared “status surface” rendering logic.
Changes:
- Introduce low-level OSC terminal-title writer with sanitization and tests.
- Add
/titleslash command and a bottom-pane picker to configure/preview title segments, persisting selection to config. - Refactor shared status-line + terminal-title rendering/state (git branch lookup, invalid-item warnings, caches) into
chatwidget/status_surfaces.rs, and attempt to preserve the last-written title cache across widget replacement.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| codex-rs/tui_app_server/src/terminal_title.rs | New OSC title writer + sanitization and unit tests. |
| codex-rs/tui_app_server/src/slash_command.rs | Adds Title slash command metadata. |
| codex-rs/tui_app_server/src/lib.rs | Wires in the new terminal_title module. |
| codex-rs/tui_app_server/src/chatwidget/tests.rs | Updates ChatWidget test builders for new title/state fields. |
| codex-rs/tui_app_server/src/chatwidget/status_surfaces.rs | New shared status-line/terminal-title parsing, caching, and rendering. |
| codex-rs/tui_app_server/src/chatwidget.rs | Integrates terminal title updates, setup flows, and shared refresh entrypoints. |
| codex-rs/tui_app_server/src/bottom_pane/title_setup.rs | New terminal-title configuration picker UI + snapshot tests. |
| codex-rs/tui_app_server/src/bottom_pane/snapshots/codex_tui_app_server__bottom_pane__title_setup__tests__terminal_title_setup_basic.snap | Snapshot for the new picker UI. |
| codex-rs/tui_app_server/src/bottom_pane/mod.rs | Exposes TerminalTitleItem + TerminalTitleSetupView. |
| codex-rs/tui_app_server/src/app_event.rs | Adds app events for title setup/preview/cancel. |
| codex-rs/tui_app_server/src/app.rs | Persists title config changes; tries to preserve title cache across widget replacement; clears title on drop. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add `/title` support to `tui_app_server`, including the title setup popup, config persistence, live terminal-title preview, and terminal OSC title updates that track runtime state. Port the shared status/title surface structure toward classic `tui` parity so title rendering, git branch lookup, and invalid-item handling are coordinated in `chatwidget/status_surfaces.rs`. Also preserve the terminal title cache across widget replacement (closing a gap vs the classic `tui` implementation) to avoid redundant OSC writes on session switches.
Document undocumented public methods, implicit contracts, and confusing constructs across the terminal title implementation. Adds rationale for MAX_TERMINAL_TITLE_CHARS, explains the double-Option setup state field, documents the strum serialization config contract, and fixes unconventional doc-after-derive placement. No runtime behavior changes.
dac4bc4 to
6eced7b
Compare
Fix the app-server TUI title setup and refresh paths so configured terminal-title items parse correctly, title/status surfaces refresh when session and branch state changes, and terminal-title sanitization keeps a visible character when the last slot would otherwise be consumed by a pending space. Add the missing `terminal_title_invalid_items_warned` test initializers, extract shared invalid-item parsing, and update the title setup snapshot and parser coverage so the reviewed behavior stays exercised.
etraut-openai
approved these changes
Mar 26, 2026
Collaborator
etraut-openai
left a comment
There was a problem hiding this comment.
Code looks good. I also built and tested it locally.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TR;DR
Replicates the
/titlecommand fromtuitotui_app_server.Problem
The classic
tuicrate supports customizing the terminal window/tab title via/title, but thetui_app_servercrate does not. Users on the app-server path have no way to configure what their terminal title shows (project name, status, spinner, thread, etc.), making it harder to identify Codex sessions across tabs or windows.Mental model
The terminal title is a status surface -- conceptually parallel to the footer status line. Both surfaces are configurable lists of items, both share expensive inputs (git branch lookup, project root discovery), and both must be refreshed at the same lifecycle points. This change ports the classic
tui's design verbatim:terminal_title.rsowns the low-level OSC write path and input sanitization. It strips control characters and bidi/invisible codepoints before placing untrusted text (model output, thread names, project paths) inside an escape sequence.title_setup.rsdefinesTerminalTitleItem(the 8 configurable items) andTerminalTitleSetupView(the interactive picker that wrapsMultiSelectPicker).status_surfaces.rsis the shared refresh pipeline. It parses both surface configs once per refresh, warns about invalid items once per session, synchronizes the git-branch cache, then renders each surface from the sameStatusSurfaceSelectionssnapshot.chatwidget.rssetsTerminalTitleStatusKindat each state transition (Working, Thinking, Undoing, WaitingForBackgroundTerminal) and callsrefresh_terminal_title()whenever relevant state changes.app.rshandles the three setup events (confirm/preview/cancel), persists config viaConfigEditsBuilder, and clears the managed title onDrop.Non-goals
Dropclears the managed title rather than restoring it.tuiandtui_app_server. The implementation is a parallel copy, matching the existing pattern for the status-line feature. Extracting a shared crate is future work.Tradeoffs
Duplicate code across crates. The three core files (
terminal_title.rs,title_setup.rs,status_surfaces.rs) are byte-for-byte copies from the classictui. This was chosen for consistency with the existing status-line port and to avoid coupling the two crates at the dependency level. Future changes must be applied in both places.status_surfaces.rsis large (~660 lines). It absorbs logic that previously lived inline inchatwidget.rs(status-line refresh, git branch management, project root discovery) plus all new terminal-title logic. This consolidation trades file size for a single place where both surfaces are coordinated.Spinner scheduling on every refresh. The terminal title spinner (when active) schedules a frame every 100ms. This is the same pattern the status-indicator spinner already uses; the overhead is a timer registration, not a redraw.
Architecture
Observability
on_warning()(gated byterminal_title_invalid_items_warnedAtomicBool).tracing::debuglevel.tracing::errorand surfaced to the user viaadd_error_message().Tests
terminal_title.rs: 4 unit tests covering sanitization (control chars, bidi codepoints, truncation) and OSC output format.title_setup.rs: 3 tests covering setup view snapshot rendering, parse order preservation, and invalid-ID rejection.chatwidget/tests.rs: Updated test helpers with new fields; existing tests continue to pass.