chore(tui): wip instrument resize render diagnostics

Add temporary resize-path diagnostics for terminal resize investigation.
The logs capture terminal identity, platform, VSCode/macOS env markers,
watchdog state, resize reflow mode, pre-render decisions, scheduled draw
events, and terminal draw start/end timing boundaries.

This is intentionally a WIP commit that should be amended as we learn
which resize path needs a real fix.
This commit is contained in:
Felipe Coury
2026-04-15 12:57:37 -03:00
parent e2b217a1a0
commit 96775bfb4b
3 changed files with 188 additions and 15 deletions
+89 -12
View File
@@ -3385,12 +3385,34 @@ impl App {
frame_requester: &tui::FrameRequester,
resize_reflow_mode: tui::ResizeReflowMode,
) -> bool {
let pending_reflow_before = self.transcript_reflow.has_pending_reflow();
let width = self.transcript_reflow.note_width(size.width);
tracing::info!(
event = "resize_width_observed",
cols = size.width,
rows = size.height,
last_known_cols = last_known_screen_size.width,
last_known_rows = last_known_screen_size.height,
width_changed = width.changed,
width_initialized = width.initialized,
pending_reflow_before,
resize_reflow_mode = ?resize_reflow_mode,
"observed terminal width during draw pre-render"
);
if width.changed {
self.chat_widget.on_terminal_resize(size.width);
match resize_reflow_mode {
tui::ResizeReflowMode::Debounced => {
if self.schedule_resize_reflow() {
let due_now = self.schedule_resize_reflow();
tracing::info!(
event = "resize_reflow_scheduled_debounced",
cols = size.width,
rows = size.height,
due_now,
debounce_ms = TRANSCRIPT_REFLOW_DEBOUNCE.as_millis(),
"scheduled debounced resize reflow"
);
if due_now {
frame_requester.schedule_frame();
} else {
frame_requester.schedule_frame_in(TRANSCRIPT_REFLOW_DEBOUNCE);
@@ -3451,7 +3473,16 @@ impl App {
let Some(deadline) = self.transcript_reflow.pending_until() else {
return Ok(());
};
if Instant::now() < deadline || self.overlay.is_some() {
let now = Instant::now();
let due = now >= deadline;
let overlay_active = self.overlay.is_some();
if !due || overlay_active {
tracing::info!(
event = "resize_reflow_deferred",
due,
overlay_active,
"resize reflow remains pending"
);
return Ok(());
}
@@ -3463,7 +3494,18 @@ impl App {
let reflow_ran_during_stream =
!self.transcript_cells.is_empty() && self.should_mark_reflow_as_stream_time();
tracing::info!(
event = "resize_reflow_started",
transcript_cells = self.transcript_cells.len(),
reflow_ran_during_stream,
"running resize reflow"
);
self.reflow_transcript_now(tui)?;
tracing::info!(
event = "resize_reflow_completed",
reflow_ran_during_stream,
"completed resize reflow"
);
if reflow_ran_during_stream {
self.transcript_reflow.mark_ran_during_stream();
@@ -4236,19 +4278,38 @@ impl App {
event: TuiEvent,
) -> Result<AppRunControl> {
let is_resize = matches!(event, TuiEvent::Resize);
let render_trigger = match &event {
TuiEvent::Draw => Some("draw"),
TuiEvent::Resize => Some("resize"),
TuiEvent::Key(_) | TuiEvent::Paste(_) => None,
};
if is_resize {
let terminal_size = tui.terminal.size().ok();
tracing::info!(
event = "tui_resize_event_handled",
cols = terminal_size.map(|size| size.width),
rows = terminal_size.map(|size| size.height),
terminal_name = ?tui.terminal_name(),
resize_reflow_mode = ?tui.resize_reflow_mode(),
target_os = std::env::consts::OS,
is_windows = cfg!(windows),
is_macos = cfg!(target_os = "macos"),
"handling TUI resize event with forced full repaint"
);
tui.force_full_repaint();
}
if matches!(event, TuiEvent::Draw | TuiEvent::Resize) {
if let Some(render_trigger) = render_trigger {
tracing::info!(
event = "tui_render_pre_render_started",
render_trigger,
"starting draw pre-render handling"
);
self.handle_draw_pre_render(tui)?;
tracing::info!(
event = "tui_render_pre_render_completed",
render_trigger,
"completed draw pre-render handling"
);
}
if self.overlay.is_some() {
@@ -4267,6 +4328,7 @@ impl App {
self.chat_widget.handle_paste(pasted);
}
TuiEvent::Draw | TuiEvent::Resize => {
let render_trigger = render_trigger.unwrap_or("unknown");
if self.backtrack_render_pending {
self.backtrack_render_pending = false;
self.render_transcript_once(tui);
@@ -4280,15 +4342,30 @@ impl App {
}
// Allow widgets to process any pending timers before rendering.
self.chat_widget.pre_draw_tick();
tui.draw(
self.chat_widget.desired_height(tui.terminal.size()?.width),
|frame| {
self.chat_widget.render(frame.area(), frame.buffer);
if let Some((x, y)) = self.chat_widget.cursor_pos(frame.area()) {
frame.set_cursor_position((x, y));
}
},
)?;
let terminal_size = tui.terminal.size()?;
let desired_height = self.chat_widget.desired_height(terminal_size.width);
tracing::info!(
event = "tui_render_started",
render_trigger,
cols = terminal_size.width,
rows = terminal_size.height,
desired_height,
"starting TUI render"
);
tui.draw(desired_height, |frame| {
self.chat_widget.render(frame.area(), frame.buffer);
if let Some((x, y)) = self.chat_widget.cursor_pos(frame.area()) {
frame.set_cursor_position((x, y));
}
})?;
tracing::info!(
event = "tui_render_completed",
render_trigger,
cols = terminal_size.width,
rows = terminal_size.height,
desired_height,
"completed TUI render"
);
if self.chat_widget.external_editor_state() == ExternalEditorState::Requested {
self.chat_widget
.set_external_editor_state(ExternalEditorState::Active);
+58 -2
View File
@@ -302,6 +302,7 @@ pub struct Tui {
notification_backend: Option<DesktopNotificationBackend>,
notification_condition: NotificationCondition,
is_zellij: bool,
terminal_name: TerminalName,
resize_reflow_mode: ResizeReflowMode,
force_full_repaint: bool,
// When false, enter_alt_screen() becomes a no-op (for Zellij scrollback support)
@@ -334,6 +335,19 @@ impl Tui {
mode = ?resize_reflow_mode,
terminal_name = ?terminal_info.name,
term_program = ?terminal_info.term_program,
term_program_version = ?terminal_info.version,
term = ?terminal_info.term,
colorterm = ?std::env::var("COLORTERM").ok(),
target_os = std::env::consts::OS,
target_arch = std::env::consts::ARCH,
is_windows = cfg!(windows),
is_macos = cfg!(target_os = "macos"),
vscode_ipc_hook_cli_present = std::env::var_os("VSCODE_IPC_HOOK_CLI").is_some(),
vscode_injection_present = std::env::var_os("VSCODE_INJECTION").is_some(),
vscode_shell_login_present = std::env::var_os("VSCODE_SHELL_LOGIN").is_some(),
vscode_cwd_present = std::env::var_os("VSCODE_CWD").is_some(),
term_session_id_present = std::env::var_os("TERM_SESSION_ID").is_some(),
lc_terminal_present = std::env::var_os("LC_TERMINAL").is_some(),
"configured terminal resize reflow mode"
);
@@ -352,6 +366,7 @@ impl Tui {
notification_backend: Some(detect_backend(NotificationMethod::default())),
notification_condition: NotificationCondition::default(),
is_zellij,
terminal_name: terminal_info.name,
resize_reflow_mode,
force_full_repaint: false,
alt_screen_enabled: true,
@@ -384,6 +399,10 @@ impl Tui {
self.resize_reflow_mode
}
pub(crate) fn terminal_name(&self) -> TerminalName {
self.terminal_name
}
pub fn enhanced_keys_supported(&self) -> bool {
self.enhanced_keys_supported
}
@@ -637,7 +656,26 @@ impl Tui {
self.pending_viewport_area()?
};
stdout().sync_update(|_| {
let terminal_size = self.terminal.size().ok();
let viewport_area = self.terminal.viewport_area;
tracing::info!(
event = "terminal_draw_started",
requested_height = height,
force_full_repaint,
pending_viewport_area = pending_viewport_area.is_some(),
pending_history_lines = self.pending_history_lines.len(),
terminal_cols = terminal_size.map(|size| size.width),
terminal_rows = terminal_size.map(|size| size.height),
viewport_x = viewport_area.x,
viewport_y = viewport_area.y,
viewport_width = viewport_area.width,
viewport_height = viewport_area.height,
terminal_name = ?self.terminal_name,
resize_reflow_mode = ?self.resize_reflow_mode,
"starting terminal draw"
);
let draw_result = stdout().sync_update(|_| {
#[cfg(unix)]
if let Some(prepared) = prepared_resume.take() {
prepared.apply(&mut self.terminal)?;
@@ -683,7 +721,25 @@ impl Tui {
terminal.draw(|frame| {
draw_fn(frame);
})
})?
})?;
match &draw_result {
Ok(()) => tracing::info!(
event = "terminal_draw_completed",
requested_height = height,
force_full_repaint,
"completed terminal draw"
),
Err(err) => tracing::warn!(
event = "terminal_draw_failed",
requested_height = height,
force_full_repaint,
error = %err,
"terminal draw failed"
),
}
draw_result
}
fn pending_viewport_area(&mut self) -> Result<Option<Rect>> {
+41 -1
View File
@@ -242,6 +242,7 @@ pub struct TuiEventStream<S: EventSource + Default + Unpin = CrosstermEventSourc
draw_stream: BroadcastStream<()>,
resume_stream: WatchStream<()>,
resize_watchdog: Option<ResizeWatchdog>,
terminal_name: TerminalName,
terminal_focused: Arc<AtomicBool>,
poll_draw_first: bool,
#[cfg(unix)]
@@ -263,6 +264,26 @@ impl<S: EventSource + Default + Unpin> TuiEventStream<S> {
let has_superset_env_markers = has_superset_env_markers();
let enable_resize_watchdog =
should_enable_superset_resize_watchdog(&terminal_info, has_superset_env_markers);
tracing::info!(
event = "terminal_resize_event_stream_configured",
terminal_name = ?terminal_info.name,
term_program = ?terminal_info.term_program,
term_program_version = ?terminal_info.version,
term = ?terminal_info.term,
colorterm = ?std::env::var("COLORTERM").ok(),
target_os = std::env::consts::OS,
target_arch = std::env::consts::ARCH,
is_windows = cfg!(windows),
is_macos = cfg!(target_os = "macos"),
vscode_ipc_hook_cli_present = std::env::var_os("VSCODE_IPC_HOOK_CLI").is_some(),
vscode_injection_present = std::env::var_os("VSCODE_INJECTION").is_some(),
vscode_shell_login_present = std::env::var_os("VSCODE_SHELL_LOGIN").is_some(),
vscode_cwd_present = std::env::var_os("VSCODE_CWD").is_some(),
term_session_id_present = std::env::var_os("TERM_SESSION_ID").is_some(),
lc_terminal_present = std::env::var_os("LC_TERMINAL").is_some(),
resize_watchdog_enabled = enable_resize_watchdog,
"configured terminal resize event stream"
);
tracing::info!(
event = "superset_resize_watchdog_configured",
enabled = enable_resize_watchdog,
@@ -279,6 +300,7 @@ impl<S: EventSource + Default + Unpin> TuiEventStream<S> {
draw_stream: BroadcastStream::new(draw_rx),
resume_stream,
resize_watchdog,
terminal_name: terminal_info.name,
terminal_focused,
poll_draw_first: false,
#[cfg(unix)]
@@ -342,8 +364,22 @@ impl<S: EventSource + Default + Unpin> TuiEventStream<S> {
/// Poll the draw broadcast stream for the next draw event. Draw events are used to trigger a redraw of the TUI.
pub fn poll_draw_event(&mut self, cx: &mut Context<'_>) -> Poll<Option<TuiEvent>> {
match Pin::new(&mut self.draw_stream).poll_next(cx) {
Poll::Ready(Some(Ok(()))) => Poll::Ready(Some(TuiEvent::Draw)),
Poll::Ready(Some(Ok(()))) => {
tracing::info!(
event = "tui_draw_event_received",
terminal_name = ?self.terminal_name,
target_os = std::env::consts::OS,
"received scheduled TUI draw event"
);
Poll::Ready(Some(TuiEvent::Draw))
}
Poll::Ready(Some(Err(BroadcastStreamRecvError::Lagged(_)))) => {
tracing::warn!(
event = "tui_draw_event_lagged",
terminal_name = ?self.terminal_name,
target_os = std::env::consts::OS,
"scheduled TUI draw event lagged"
);
Poll::Ready(Some(TuiEvent::Draw))
}
Poll::Ready(None) => Poll::Ready(None),
@@ -388,6 +424,10 @@ impl<S: EventSource + Default + Unpin> TuiEventStream<S> {
event = "crossterm_resize_event_received",
cols,
rows,
terminal_name = ?self.terminal_name,
target_os = std::env::consts::OS,
is_windows = cfg!(windows),
is_macos = cfg!(target_os = "macos"),
watchdog_enabled = self.resize_watchdog.is_some(),
"received crossterm terminal resize event"
);