fix(tui): order transient token cards after active cells

This commit is contained in:
Felipe Coury
2026-05-30 21:09:19 -03:00
parent 7b17e1eac8
commit bed3ed7e3d
4 changed files with 75 additions and 5 deletions
+2
View File
@@ -1177,6 +1177,7 @@ impl ChatWidget {
if let Some(active) = self.transcript.active_cell.take() {
self.transcript.needs_final_message_separator = true;
self.app_event_tx.send(AppEvent::InsertHistoryCell(active));
self.request_completed_token_activity_output_insertion();
}
}
@@ -1388,6 +1389,7 @@ impl ChatWidget {
tool.mark_failed();
}
self.add_boxed_history(cell);
self.request_completed_token_activity_output_insertion();
}
}
+6 -4
View File
@@ -23,11 +23,11 @@ impl ChatWidget {
}
_ => RenderableItem::Owned(Box::new(())),
};
let mut flex = FlexRenderable::new();
flex.push(/*flex*/ 1, active_cell_renderable);
flex.push(/*flex*/ 0, active_hook_cell_renderable);
let mut content = FlexRenderable::new();
content.push(/*flex*/ 0, active_cell_renderable);
content.push(/*flex*/ 0, active_hook_cell_renderable);
if let Some(cell) = self.pending_token_activity_output() {
flex.push(
content.push(
/*flex*/ 1,
RenderableItem::Owned(Box::new(TranscriptAreaRenderable {
child: cell,
@@ -36,6 +36,8 @@ impl ChatWidget {
})),
);
}
let mut flex = FlexRenderable::new();
flex.push(/*flex*/ 1, RenderableItem::Owned(Box::new(content)));
flex.push(
/*flex*/ 0,
RenderableItem::Owned(Box::new(BottomPaneComposerReserveRenderable {
@@ -1298,6 +1298,36 @@ async fn completed_token_activity_refresh_waits_for_active_stream() {
assert!(chat.take_completed_token_activity_output().is_some());
}
#[tokio::test]
async fn completed_token_activity_refresh_waits_for_active_history_cell() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
set_chatgpt_auth(&mut chat);
chat.dispatch_command(SlashCommand::Tokens);
let request_id = match rx.try_recv() {
Ok(AppEvent::RefreshTokenActivity { request_id }) => request_id,
other => panic!("expected token activity refresh request, got {other:?}"),
};
chat.transcript.active_cell = Some(Box::new(PlainHistoryCell::new(vec![Line::from(
"active tool",
)])));
assert!(
chat.finish_token_activity_refresh(
request_id,
Err("token activity unavailable".to_string()),
)
);
assert!(chat.token_activity_history_insertion_blocked());
chat.flush_active_cell();
assert_matches!(rx.try_recv(), Ok(AppEvent::InsertHistoryCell(_)));
assert_matches!(
rx.try_recv(),
Ok(AppEvent::CommitCompletedTokenActivityOutput)
);
}
#[tokio::test]
async fn completed_token_activity_refresh_retries_after_plan_item_completion() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
@@ -1330,6 +1360,40 @@ async fn completed_token_activity_refresh_retries_after_plan_item_completion() {
);
}
#[tokio::test]
async fn pending_token_activity_refresh_keeps_composer_visible_in_short_viewport() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
set_chatgpt_auth(&mut chat);
chat.transcript.active_cell = Some(Box::new(PlainHistoryCell::new(
std::iter::repeat_n(Line::from("active output"), /*n*/ 20).collect(),
)));
chat.dispatch_command(SlashCommand::Tokens);
assert_matches!(
rx.try_recv(),
Ok(AppEvent::RefreshTokenActivity { .. }),
"expected token activity refresh request"
);
let width: u16 = 80;
let height: u16 = 8;
let backend = VT100Backend::new(width, height);
let mut term = crate::custom_terminal::Terminal::with_options(backend).expect("terminal");
term.set_viewport_area(Rect::new(/*x*/ 0, /*y*/ 0, width, height));
term.draw(|f| {
chat.render(f.area(), f.buffer_mut());
})
.unwrap();
assert!(
term.backend()
.vt100()
.screen()
.contents()
.contains("Ask Codex to do anything")
);
}
#[tokio::test]
async fn repeated_token_activity_refreshes_keep_only_latest_card() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
+3 -1
View File
@@ -685,7 +685,9 @@ impl ChatWidget {
}
pub(crate) fn token_activity_history_insertion_blocked(&self) -> bool {
self.stream_controller.is_some() || self.plan_stream_controller.is_some()
self.stream_controller.is_some()
|| self.plan_stream_controller.is_some()
|| self.transcript.active_cell.is_some()
}
pub(crate) fn take_completed_token_activity_output(&mut self) -> Option<CompositeHistoryCell> {