Wrap built-in TUI status lines within the footer row budget (#60682)

## Why

Long built-in status lines were truncated to a single row even when the footer had room for more content.

## What changed

- Wrap status text to the available width and row budget, preserving styles and hyperlink targets across rows.
- Show an unlinked ellipsis when content exceeds the available space.
- Refresh wrapped content when the status text, agent label, or width changes.
- Keep reasoning-effort transitions on one row and restore wrapping after the final animation frame.

## Testing

Add coverage for wrapping and hyperlink boundaries across footer layouts, content and agent-label updates, resizing, and wrapping restoration after an effort transition. Update transcript and startup snapshots for multiline status text.

GitOrigin-RevId: 30e0795c473efd62fc4d0dca4deb6029b625a9e8
This commit is contained in:
Joe Savona
2026-11-10 23:35:28 +00:00
committed by copyberry
parent 192e2ef3ce
commit 65dbdb5f03
17 changed files with 380 additions and 45 deletions
@@ -503,6 +503,8 @@ async fn owned_drag_stops_when_focus_or_input_ownership_is_lost() -> Result<()>
async fn owned_details_keep_the_composer_cursor_and_screen() -> Result<()> {
let mut app = crate::app::test_support::make_test_app().await;
attach_thread(&mut app, ThreadId::new());
app.chat_widget
.set_status_line(Some("gpt-test default · /tmp/project".into()));
app.transcript_cells = vec![user_cell("First prompt"), user_cell("Second prompt")];
app.chat_widget
.apply_external_edit("preserved draft".to_string());
@@ -647,8 +649,7 @@ async fn owned_details_keep_the_composer_cursor_and_screen() -> Result<()> {
app.transcript_view.link_at(column, row).as_deref(),
Some("https://example.com/docs")
);
let copied_cursor = tui.terminal.last_known_cursor_pos;
assert_eq!((copied_cursor.x, copied_cursor.y + 1), (caret.x, caret.y));
assert_eq!(tui.terminal.last_known_cursor_pos, caret);
let buffer = crate::custom_terminal::test_support::last_rendered_buffer(&tui.terminal);
for pos in Rect::from(outer)
.positions()