17 Commits

  • 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
    `&`.
    
    ## 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`
  • 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`.
  • 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.
  • 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.
  • 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.
  • 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"
    />
  • (tui): Decode percent-escaped bare local file links (#16810)
    Addresses #16622
    
    Problem: bare local file links in TUI markdown render percent-encoded
    path bytes literally, unlike file:// links.
    
    Solution: decode bare path targets before local-path expansion and add
    regression coverage for spaces and Unicode.
  • Rename tui_app_server to tui (#16104)
    This is a follow-up to https://github.com/openai/codex/pull/15922. That
    previous PR deleted the old `tui` directory and left the new
    `tui_app_server` directory in place. This PR renames `tui_app_server` to
    `tui` and fixes up all references.
  • Remove the legacy TUI split (#15922)
    This is the part 1 of 2 PRs that will delete the `tui` /
    `tui_app_server` split. This part simply deletes the existing `tui`
    directory and marks the `tui_app_server` feature flag as removed. I left
    the `tui_app_server` feature flag in place for now so its presence
    doesn't result in an error. It is simply ignored.
    
    Part 2 will rename the `tui_app_server` directory `tui`. I did this as
    two parts to reduce visible code churn.
  • tui: restore visible line numbers for hidden file links (#12870)
    we recently changed file linking so the model uses markdown links when
    it wants something to be clickable.
    
    This works well across the GUI surfaces because they can render markdown
    cleanly and use the full absolute path in the anchor target.
    
    A previous pass hid the absolute path in the TUI (and only showed the
    label), but that also meant we could lose useful location info when the
    model put the line number or range in the anchor target instead of the
    label.
    
    This follow-up keeps the TUI behavior simple while making local file
    links feel closer to the old TUI file reference style.
    
    key changes:
    - Local markdown file links in the TUI keep the old file-ref feel: code
    styling, no underline, no visible absolute path.
    - If the hidden local anchor target includes a location suffix and the
    label does not already include one, we append that suffix to the visible
    label.
    - This works for single lines, line/column references, and ranges.
    - If the label already includes the location, we leave it alone.
    - normal web links keep the old TUI markdown-link behavior
    
    some examples:
    - `[foo.rs](/abs/path/foo.rs)` renders as `foo.rs`
    - `[foo.rs](/abs/path/foo.rs:45)` renders as `foo.rs:45`
    - `[foo.rs](/abs/path/foo.rs:45:3-48:9)` renders as `foo.rs:45:3-48:9`
    - `[foo.rs:45](/abs/path/foo.rs:45)` stays `foo.rs:45`
    - `[docs](https://example.com/docs)` still renders like a normal web
    link
    
    how it looks:
    <img width="732" height="813" alt="Screenshot 2026-02-26 at 9 27 55 AM"
    src="https://github.com/user-attachments/assets/d51bf236-653a-4e83-96e4-9427f0804471"
    />
  • Hide local file link destinations in TUI markdown (#12705)
    ## Summary
    - hide appended destinations for local path-style markdown links in the
    TUI renderer
    - keep web links rendering with their visible destination and style link
    labels consistently
    - add markdown renderer tests and a snapshot for the new file-link
    output
    
    ## Testing
    - just fmt
    - cargo test -p codex-tui
    <img width="1120" height="968" alt="image"
    src="https://github.com/user-attachments/assets/490e8eda-ae47-4231-89fa-b254a1f83eed"
    />
  • feat(tui): syntax highlighting via syntect with theme picker (#11447)
    ## Summary
    
    Adds syntax highlighting to the TUI for fenced code blocks in markdown
    responses and file diffs, plus a `/theme` command with live preview and
    persistent theme selection. Uses syntect (~250 grammars, 32 bundled
    themes, ~1 MB binary cost) — the same engine behind `bat`, `delta`, and
    `xi-editor`. Includes guardrails for large inputs, graceful fallback to
    plain text, and SSH-aware clipboard integration for the `/copy` command.
    
    <img width="1554" height="1014" alt="image"
    src="https://github.com/user-attachments/assets/38737a79-8717-4715-b857-94cf1ba59b85"
    />
    
    <img width="2354" height="1374" alt="image"
    src="https://github.com/user-attachments/assets/25d30a00-c487-4af8-9cb6-63b0695a4be7"
    />
    
    ## Problem
    
    Code blocks in the TUI (markdown responses and file diffs) render
    without syntax highlighting, making it hard to scan code at a glance.
    Users also have no way to pick a color theme that matches their terminal
    aesthetic.
    
    ## Mental model
    
    The highlighting system has three layers:
    
    1. **Syntax engine** (`render::highlight`) -- a thin wrapper around
    syntect + two-face. It owns a process-global `SyntaxSet` (~250 grammars)
    and a `RwLock<Theme>` that can be swapped at runtime. All public entry
    points accept `(code, lang)` and return ratatui `Span`/`Line` vectors or
    `None` when the language is unrecognized or the input exceeds safety
    guardrails.
    
    2. **Rendering consumers** -- `markdown_render` feeds fenced code blocks
    through the engine; `diff_render` highlights Add/Delete content as a
    whole file and Update hunks per-hunk (preserving parser state across
    hunk lines). Both callers fall back to plain unstyled text when the
    engine returns `None`.
    
    3. **Theme lifecycle** -- at startup the config's `tui.theme` is
    resolved to a syntect `Theme` via `set_theme_override`. At runtime the
    `/theme` picker calls `set_syntax_theme` to swap themes live; on cancel
    it restores the snapshot taken at open. On confirm it persists `[tui]
    theme = "..."` to config.toml.
    
    ## Non-goals
    
    - Inline diff highlighting (word-level change detection within a line).
    - Semantic / LSP-backed highlighting.
    - Theme authoring tooling; users supply standard `.tmTheme` files.
    
    ## Tradeoffs
    
    | Decision | Upside | Downside |
    | ------------------------------------------------ |
    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    |
    -----------------------------------------------------------------------------------------------------------------------
    |
    | syntect over tree-sitter / arborium | ~1 MB binary increase for ~250
    grammars + 32 themes; battle-tested crate powering widely-used tools
    (`bat`, `delta`, `xi-editor`). tree-sitter would add ~12 MB for 20-30
    languages or ~35 MB for full coverage. | Regex-based; less structurally
    accurate than tree-sitter for some languages (e.g. language injections
    like JS-in-HTML). |
    | Global `RwLock<Theme>` | Enables live `/theme` preview without
    threading Theme through every call site | Lock contention risk
    (mitigated: reads vastly outnumber writes, single UI thread) |
    | Skip background / italic / underline from themes | Terminal BG
    preserved, avoids ugly rendering on some themes | Themes that rely on
    these properties lose fidelity |
    | Guardrails: 512 KB / 10k lines | Prevents pathological stalls on huge
    diffs or pastes | Very large files render without color |
    
    ## Architecture
    
    ```
    config.toml  ─[tui.theme]─>  set_theme_override()  ─>  THEME (RwLock)
                                                                  │
                      ┌───────────────────────────────────────────┘
                      │
      markdown_render ─── highlight_code_to_lines(code, lang) ─> Vec<Line>
      diff_render     ─── highlight_code_to_styled_spans(code, lang) ─> Option<Vec<Vec<Span>>>
                      │
                      │   (None ⇒ plain text fallback)
                      │
      /theme picker   ─── set_syntax_theme(theme)    // live preview swap
                      ─── current_syntax_theme()      // snapshot for cancel
                      ─── resolve_theme_by_name(name) // lookup by kebab-case
    ```
    
    Key files:
    
    - `tui/src/render/highlight.rs` -- engine, theme management, guardrails
    - `tui/src/diff_render.rs` -- syntax-aware diff line wrapping
    - `tui/src/theme_picker.rs` -- `/theme` command builder
    - `tui/src/bottom_pane/list_selection_view.rs` -- side content panel,
    callbacks
    - `core/src/config/types.rs` -- `Tui::theme` field
    - `core/src/config/edit.rs` -- `syntax_theme_edit()` helper
    
    ## Observability
    
    - `tracing::warn` when a configured theme name cannot be resolved.
    - `Config::startup_warnings` surfaces the same message as a TUI banner.
    - `tracing::error` when persisting theme selection fails.
    
    ## Tests
    
    - Unit tests in `highlight.rs`: language coverage, fallback behavior,
    CRLF stripping, style conversion, guardrail enforcement, theme name
    mapping exhaustiveness.
    - Unit tests in `diff_render.rs`: snapshot gallery at multiple terminal
    sizes (80x24, 94x35, 120x40), syntax-highlighted wrapping, large-diff
    guardrail, rename-to-different-extension highlighting, parser state
    preservation across hunk lines.
    - Unit tests in `theme_picker.rs`: preview rendering (wide + narrow),
    dim overlay on deletions, subtitle truncation, cancel-restore, fallback
    for unavailable configured theme.
    - Unit tests in `list_selection_view.rs`: side layout geometry, stacked
    fallback, buffer clearing, cancel/selection-changed callbacks.
    - Integration test in `lib.rs`: theme warning uses the final
    (post-resume) config.
    
    ## Cargo Deny: Unmaintained Dependency Exceptions
    
    This PR adds two `cargo deny` advisory exceptions for transitive
    dependencies pulled in by `syntect v5.3.0`:
    
    | Advisory | Crate | Status |
    |----------|-------|--------|
    | RUSTSEC-2024-0320 | `yaml-rust` | Unmaintained (maintainer
    unreachable) |
    | RUSTSEC-2025-0141 | `bincode` | Unmaintained (development ceased;
    v1.3.3 considered complete) |
    
    **Why this is safe in our usage:**
    
    - Neither advisory describes a known security vulnerability. Both are
    "unmaintained" notices only.
    - `bincode` is used by syntect to deserialize pre-compiled syntax sets.
    Again, these are **static vendored artifacts** baked into the binary at
    build time. No user-supplied bincode data is ever deserialized. - Attack
    surface is zero for both crates; exploitation would require a
    supply-chain compromise of our own build artifacts.
    - These exceptions can be removed when syntect migrates to `yaml-rust2`
    and drops `bincode`, or when alternative crates are available upstream.
  • replace tui_markdown with a custom markdown renderer (#3396)
    Also, simplify the streaming behavior.
    
    This fixes a number of display issues with streaming markdown, and paves
    the way for better markdown features (e.g. customizable styles, syntax
    highlighting, markdown-aware wrapping).
    
    Not currently supported:
    - footnotes
    - tables
    - reference-style links