Render the initial TUI session header before input (#38639)

## What changed

- Drain queued app events until the initial session header is installed before
  handling terminal input.
- Render the active session header without a leading separator so it starts at
  the top of the viewport.

## Testing

- Add a rendering snapshot that verifies the initial header begins on the first
  viewport row.

GitOrigin-RevId: 070417776b629c681b3f94e93ba619dfefe764b1
This commit is contained in:
Charlie Marsh authored and copyberry committed 2026-08-14 20:19:42 +00:00
1 parent ccee70f813
commit 7e65e4f330
3 files changed
+57 -3

No files matched your search

+6 -1
View File
@@ -11,7 +11,12 @@ impl ChatWidget {
let active_cell_renderable = match &self.transcript.active_cell {
Some(cell) => RenderableItem::Owned(Box::new(TranscriptAreaRenderable {
child: cell.as_ref(),
top: 1,
// The initial header becomes the first history cell, which has no leading separator.
top: if cell.as_any().is::<history_cell::SessionHeaderHistoryCell>() {
0
} else {
1
},
right: active_cell_right_reserve,
// Externally backed transcript cells can also change viewport height without an
// active-cell revision. Spinner cells remain safe because their indicator width
@@ -75,6 +75,41 @@ fn contains_text(buffer: &Buffer, text: &str) -> bool {
})
}
#[tokio::test]
async fn initial_session_header_starts_at_the_top_of_the_viewport() {
let (mut widget, _sender, _events, _operations) = make_chatwidget_manual_with_sender().await;
widget.transcript.active_cell =
Some(ChatWidget::placeholder_session_header_cell(&widget.config));
let frame = render_frame(&widget, /*width*/ 48);
let header = frame
.content
.chunks(usize::from(frame.area.width))
.take(/*n*/ 6)
.map(|row| {
row.iter()
.map(ratatui::buffer::Cell::symbol)
.collect::<String>()
.trim_end()
.to_string()
})
.collect::<Vec<_>>()
.join("\n")
.replace(crate::version::CODEX_CLI_VERSION, "<VERSION>");
let cwd = widget.config.cwd.as_path().display().to_string();
let normalized_cwd = format!("{:<width$}", "/tmp/project", width = cwd.len());
insta::assert_snapshot!(header.replace(&cwd, &normalized_cwd), @r"
╭───────────────────────────────────────╮
│ >_ OpenAI Codex (v<VERSION>) │
│ │
│ model: loading /model to change │
│ directory: /tmp/project │
╰───────────────────────────────────────╯
");
}
#[tokio::test]
async fn active_cell_layout_reuses_heights_without_freezing_animation() {
let (widget, desired_height_calls, display_lines_calls) = widget_with_counting_cell(