wip(tui): remove resize reflow slow threshold

This commit is contained in:
Felipe Coury
2026-04-23 15:42:09 -03:00
parent bee6ba1e6b
commit c4daad5a98
10 changed files with 21 additions and 392 deletions
-7
View File
@@ -532,19 +532,12 @@ pub struct ModelAvailabilityNuxConfig {
pub shown_count: HashMap<String, u32>,
}
pub const DEFAULT_TERMINAL_RESIZE_REFLOW_SLOW_THRESHOLD_MS: u64 = 400;
pub const DEFAULT_TERMINAL_RESIZE_REFLOW_MAX_ROWS: usize = 12_000;
/// Experimental terminal resize-reflow tuning knobs.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct TerminalResizeReflowToml {
/// Disable resize reflow for the current transcript when a reflow operation exceeds this many
/// milliseconds. Omit or set to `0` to disable the slow-reflow time limit.
#[serde(default)]
#[schemars(range(min = 0))]
pub slow_threshold_ms: Option<u64>,
/// Trim resize-reflow replay to the most recent rendered terminal rows when the transcript
/// exceeds this cap. Omit or set to `0` to keep all rendered rows.
#[serde(default)]
+1 -9
View File
@@ -1943,13 +1943,6 @@
"format": "uint",
"minimum": 0.0,
"type": "integer"
},
"slow_threshold_ms": {
"default": null,
"description": "Disable resize reflow for the current transcript when a reflow operation exceeds this many milliseconds. Omit or set to `0` to disable the slow-reflow time limit.",
"format": "uint64",
"minimum": 0.0,
"type": "integer"
}
},
"type": "object"
@@ -2091,8 +2084,7 @@
}
],
"default": {
"max_rows": null,
"slow_threshold_ms": null
"max_rows": null
},
"description": "Experimental terminal resize-reflow tuning knobs."
},
+1 -12
View File
@@ -507,7 +507,6 @@ fn config_toml_deserializes_model_availability_nux() {
fn config_toml_deserializes_terminal_resize_reflow_config() {
let toml = r#"
[tui.terminal_resize_reflow]
slow_threshold_ms = 350
max_rows = 9000
"#;
let cfg: ConfigToml =
@@ -518,7 +517,6 @@ max_rows = 9000
.expect("tui config should deserialize")
.terminal_resize_reflow,
TerminalResizeReflowToml {
slow_threshold_ms: Some(350),
max_rows: Some(9000),
}
);
@@ -1342,7 +1340,6 @@ async fn runtime_config_resolves_terminal_resize_reflow_defaults_and_overrides()
ConfigToml {
tui: Some(Tui {
terminal_resize_reflow: TerminalResizeReflowToml {
slow_threshold_ms: Some(350),
max_rows: Some(9000),
},
..Default::default()
@@ -1355,19 +1352,12 @@ async fn runtime_config_resolves_terminal_resize_reflow_defaults_and_overrides()
.await
.expect("load overridden config");
assert_eq!(
cfg.terminal_resize_reflow.slow_threshold,
Some(Duration::from_millis(/*millis*/ 350))
);
assert_eq!(cfg.terminal_resize_reflow.max_rows, Some(9000));
let cfg = Config::load_from_base_config_with_overrides(
ConfigToml {
tui: Some(Tui {
terminal_resize_reflow: TerminalResizeReflowToml {
slow_threshold_ms: Some(0),
max_rows: Some(0),
},
terminal_resize_reflow: TerminalResizeReflowToml { max_rows: Some(0) },
..Default::default()
}),
..Default::default()
@@ -1378,7 +1368,6 @@ async fn runtime_config_resolves_terminal_resize_reflow_defaults_and_overrides()
.await
.expect("load config with disabled resize reflow limits");
assert_eq!(cfg.terminal_resize_reflow.slow_threshold, None);
assert_eq!(cfg.terminal_resize_reflow.max_rows, None);
}
+1 -17
View File
@@ -93,7 +93,6 @@ use std::io::ErrorKind;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use crate::config::permissions::compile_permission_profile;
use crate::config::permissions::get_readable_roots_required_for_codex_runtime;
@@ -626,21 +625,11 @@ impl Default for MultiAgentV2Config {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TerminalResizeReflowConfig {
pub slow_threshold: Option<Duration>,
pub max_rows: Option<usize>,
}
impl Default for TerminalResizeReflowConfig {
fn default() -> Self {
Self {
slow_threshold: None,
max_rows: None,
}
}
}
impl AuthManagerConfig for Config {
fn codex_home(&self) -> PathBuf {
self.codex_home.to_path_buf()
@@ -1505,11 +1494,6 @@ fn resolve_terminal_resize_reflow_config(config_toml: &ConfigToml) -> TerminalRe
};
TerminalResizeReflowConfig {
slow_threshold: tui
.terminal_resize_reflow
.slow_threshold_ms
.and_then(|millis| (millis > 0).then(|| Duration::from_millis(millis)))
.or(defaults.slow_threshold),
max_rows: tui
.terminal_resize_reflow
.max_rows
-1
View File
@@ -1175,7 +1175,6 @@ impl App {
frame.set_cursor_position((x, y));
}
})?;
self.maybe_disable_slow_resize_reflow_flush(stats);
self.maybe_finish_initial_history_replay_measurement(stats);
} else {
tui.draw(desired_height, |frame| {
+2 -15
View File
@@ -30,7 +30,7 @@ impl App {
previous_resize_reflow_config != config.terminal_resize_reflow;
self.config = config;
if resize_reflow_config_changed {
self.transcript_reflow.clear_runtime_disable();
self.transcript_reflow.clear_row_cap_trim_warning();
}
self.chat_widget.sync_plugin_mentions_config(&self.config);
Ok(())
@@ -554,7 +554,6 @@ mod tests {
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::SessionConfiguredEvent;
use pretty_assertions::assert_eq;
use std::time::Duration;
use tempfile::tempdir;
#[tokio::test]
@@ -669,33 +668,21 @@ mod tests {
}
#[tokio::test]
async fn refresh_in_memory_config_from_disk_updates_resize_reflow_config_and_clears_disable()
-> Result<()> {
async fn refresh_in_memory_config_from_disk_updates_resize_reflow_config() -> Result<()> {
let mut app = make_test_app().await;
let codex_home = tempdir()?;
app.config.codex_home = codex_home.path().to_path_buf().abs();
app.transcript_reflow.record_elapsed(
crate::transcript_reflow::ResizeReflowDisableReason::RenderSlow,
Duration::from_millis(/*millis*/ 251),
Duration::from_millis(/*millis*/ 250),
);
std::fs::write(
codex_home.path().join("config.toml"),
r#"
[tui.terminal_resize_reflow]
slow_threshold_ms = 350
max_rows = 9000
"#,
)?;
app.refresh_in_memory_config_from_disk().await?;
assert_eq!(
app.config.terminal_resize_reflow.slow_threshold,
Some(Duration::from_millis(/*millis*/ 350))
);
assert_eq!(app.config.terminal_resize_reflow.max_rows, Some(9000));
assert!(!app.transcript_reflow.is_runtime_disabled());
Ok(())
}
+11 -94
View File
@@ -23,7 +23,6 @@ use super::InitialHistoryReplayMetrics;
use super::trailing_run_start;
use crate::history_cell;
use crate::history_cell::HistoryCell;
use crate::transcript_reflow::ResizeReflowDisableReason;
use crate::transcript_reflow::TRANSCRIPT_REFLOW_DEBOUNCE;
use crate::transcript_reflow::TranscriptReflowKind;
use crate::tui;
@@ -92,7 +91,7 @@ impl App {
}
pub(super) fn terminal_resize_reflow_active(&self) -> bool {
self.terminal_resize_reflow_enabled() && !self.transcript_reflow.is_runtime_disabled()
self.terminal_resize_reflow_enabled()
}
pub(super) fn begin_initial_history_replay_measurement(&mut self) {
@@ -211,11 +210,9 @@ impl App {
return;
}
let metrics = self
.initial_history_replay_metrics
.take()
.expect("metrics checked above");
self.show_initial_history_replay_measurement(metrics);
if let Some(metrics) = self.initial_history_replay_metrics.take() {
self.show_initial_history_replay_measurement(metrics);
}
}
fn show_initial_history_replay_measurement(&mut self, metrics: InitialHistoryReplayMetrics) {
@@ -252,69 +249,10 @@ impl App {
.schedule_debounced(kind, target_width)
}
fn resize_reflow_slow_threshold(&self) -> Option<Duration> {
self.config.terminal_resize_reflow.slow_threshold
}
fn resize_reflow_max_rows(&self) -> Option<usize> {
self.config.terminal_resize_reflow.max_rows
}
pub(super) fn maybe_disable_slow_resize_reflow_flush(
&mut self,
stats: tui::ResizeReflowDrawStats,
) {
let Some(threshold) = self.resize_reflow_slow_threshold() else {
return;
};
if !stats.flushed_reflow_history
|| !self.transcript_reflow.record_elapsed(
ResizeReflowDisableReason::FlushSlow,
stats.reflow_flush_elapsed,
threshold,
)
{
return;
}
tracing::warn!(
elapsed_ms = stats.reflow_flush_elapsed.as_millis(),
threshold_ms = threshold.as_millis(),
reason = ?self.transcript_reflow.runtime_disabled_reason(),
"terminal resize reflow disabled for current transcript after slow terminal flush"
);
self.maybe_show_slow_resize_reflow_disabled_warning(stats.reflow_flush_elapsed, threshold);
}
fn maybe_disable_slow_resize_reflow_render(
&mut self,
elapsed: Duration,
stats: ResizeReflowRunStats,
) {
let Some(threshold) = self.resize_reflow_slow_threshold() else {
return;
};
if !self.transcript_reflow.record_elapsed(
ResizeReflowDisableReason::RenderSlow,
elapsed,
threshold,
) {
return;
}
tracing::warn!(
elapsed_ms = elapsed.as_millis(),
threshold_ms = threshold.as_millis(),
kind = ?stats.kind,
width = stats.width,
cell_count = stats.cell_count,
rendered_line_count = stats.rendered_line_count,
reason = ?self.transcript_reflow.runtime_disabled_reason(),
"terminal resize reflow disabled for current transcript after slow render"
);
self.maybe_show_slow_resize_reflow_disabled_warning(elapsed, threshold);
}
fn maybe_note_row_cap_limited_reflow(&mut self, stats: ResizeReflowRunStats) {
if !stats.row_cap_limited {
return;
@@ -334,23 +272,6 @@ impl App {
self.maybe_show_row_cap_resize_reflow_trimmed_warning(stats.rendered_line_count, max_rows);
}
fn maybe_show_slow_resize_reflow_disabled_warning(
&mut self,
elapsed: Duration,
threshold: Duration,
) {
if self.transcript_reflow.take_runtime_disable_warning_needed() {
self.chat_widget.add_info_message(
format!(
"Terminal resize reflow paused for this session because resizing took {}ms, above the {}ms limit. Future resizes will use legacy terminal behavior until the transcript is cleared.",
elapsed.as_millis(),
threshold.as_millis()
),
/*hint*/ None,
);
}
}
fn maybe_show_row_cap_resize_reflow_trimmed_warning(
&mut self,
kept_line_count: usize,
@@ -359,8 +280,7 @@ impl App {
if self.transcript_reflow.take_row_cap_trim_warning_needed() {
self.chat_widget.add_info_message(
format!(
"Terminal resize reflow limited scrollback to the most recent {kept_line_count} rows before rendering because the row cap is {}.",
max_rows,
"Terminal resize reflow limited scrollback to the most recent {kept_line_count} rows before rendering because the row cap is {max_rows}.",
),
/*hint*/ None,
);
@@ -590,19 +510,16 @@ impl App {
};
let elapsed = started.elapsed();
self.show_resize_reflow_timing_debug_message(elapsed, stats);
self.maybe_disable_slow_resize_reflow_render(elapsed, stats);
self.transcript_reflow.mark_reflowed_width(stats.width);
if reflow_ran_during_stream && !self.transcript_reflow.is_runtime_disabled() {
if reflow_ran_during_stream {
self.transcript_reflow.mark_ran_during_stream();
}
if !self.transcript_reflow.is_runtime_disabled() {
// Some terminals settle their final reported width after the repaint that handled the
// last resize event. Request one cheap follow-up draw so `handle_draw_pre_render` can
// sample that width and schedule a final reflow if needed.
tui.frame_requester()
.schedule_frame_in(TRANSCRIPT_REFLOW_DEBOUNCE);
}
// Some terminals settle their final reported width after the repaint that handled the
// last resize event. Request one cheap follow-up draw so `handle_draw_pre_render` can
// sample that width and schedule a final reflow if needed.
tui.frame_requester()
.schedule_frame_in(TRANSCRIPT_REFLOW_DEBOUNCE);
Ok(())
}
-77
View File
@@ -3691,14 +3691,6 @@ fn enable_terminal_resize_reflow(app: &mut App) {
.expect("feature should be configurable");
}
fn disable_resize_reflow_for_test(app: &mut App) {
assert!(app.transcript_reflow.record_elapsed(
crate::transcript_reflow::ResizeReflowDisableReason::RenderSlow,
std::time::Duration::from_millis(/*millis*/ 251),
std::time::Duration::from_millis(/*millis*/ 250),
));
}
fn plain_line_cell(text: impl Into<String>) -> Arc<dyn HistoryCell> {
Arc::new(PlainHistoryCell::new(vec![Line::from(text.into())])) as Arc<dyn HistoryCell>
}
@@ -4540,37 +4532,6 @@ async fn replace_chat_widget_reseeds_collab_agent_metadata_for_replay() {
);
}
#[tokio::test]
async fn replace_chat_widget_preserves_resize_reflow_runtime_disable() {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
enable_terminal_resize_reflow(&mut app);
disable_resize_reflow_for_test(&mut app);
let replacement = ChatWidget::new_with_app_event(ChatWidgetInit {
config: app.config.clone(),
frame_requester: crate::tui::FrameRequester::test_dummy(),
app_event_tx: app.app_event_tx.clone(),
initial_user_message: None,
enhanced_keys_supported: app.enhanced_keys_supported,
has_chatgpt_account: app.chat_widget.has_chatgpt_account(),
model_catalog: app.model_catalog.clone(),
feedback: app.feedback.clone(),
is_first_run: false,
status_account_display: app.chat_widget.status_account_display().cloned(),
initial_plan_type: app.chat_widget.current_plan_type(),
model: Some(app.chat_widget.current_model().to_string()),
startup_tooltip_override: None,
status_line_invalid_items_warned: app.status_line_invalid_items_warned.clone(),
terminal_title_invalid_items_warned: app.terminal_title_invalid_items_warned.clone(),
session_telemetry: app.session_telemetry.clone(),
});
app.replace_chat_widget(replacement);
assert!(app.transcript_reflow.is_runtime_disabled());
assert!(!app.terminal_resize_reflow_active());
}
#[tokio::test]
async fn refreshed_snapshot_session_persists_resumed_turns() {
let mut app = make_test_app().await;
@@ -4903,44 +4864,6 @@ async fn clear_only_ui_reset_preserves_chat_session_state() {
assert_eq!(app.chat_widget.composer_text_with_pending(), "draft prompt");
}
#[tokio::test]
async fn runtime_disabled_resize_reflow_is_inactive_even_when_feature_enabled() {
let mut app = make_test_app().await;
enable_terminal_resize_reflow(&mut app);
assert!(app.terminal_resize_reflow_enabled());
assert!(app.terminal_resize_reflow_active());
disable_resize_reflow_for_test(&mut app);
assert!(app.terminal_resize_reflow_enabled());
assert!(!app.terminal_resize_reflow_active());
}
#[tokio::test]
async fn reset_app_ui_state_after_clear_resets_resize_reflow_runtime_disable() {
let mut app = make_test_app().await;
enable_terminal_resize_reflow(&mut app);
disable_resize_reflow_for_test(&mut app);
app.reset_app_ui_state_after_clear();
assert!(!app.transcript_reflow.is_runtime_disabled());
assert!(app.terminal_resize_reflow_active());
}
#[tokio::test]
async fn reset_transcript_state_after_clear_resets_resize_reflow_runtime_disable() {
let mut app = make_test_app().await;
enable_terminal_resize_reflow(&mut app);
disable_resize_reflow_for_test(&mut app);
app.reset_transcript_state_after_clear();
assert!(!app.transcript_reflow.is_runtime_disabled());
assert!(app.terminal_resize_reflow_active());
}
#[tokio::test]
async fn session_summary_skips_when_no_usage_or_resume_hint() {
assert!(
+5 -155
View File
@@ -26,8 +26,6 @@ pub(crate) struct TranscriptReflowState {
pending_kind: Option<TranscriptReflowKind>,
ran_during_stream: bool,
resize_requested_during_stream: bool,
runtime_disabled: Option<ResizeReflowDisableReason>,
runtime_disable_warning_shown: bool,
row_cap_trim_warning_shown: bool,
}
@@ -40,15 +38,6 @@ pub(crate) enum TranscriptReflowKind {
Full,
}
/// Describes why resize reflow was disabled for the current transcript lifetime.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ResizeReflowDisableReason {
/// Re-rendering transcript cells exceeded the slow-reflow threshold.
RenderSlow,
/// Writing reflowed rows back into the terminal exceeded the slow-reflow threshold.
FlushSlow,
}
impl TranscriptReflowState {
/// Reset all width, pending deadline, and stream repair state.
///
@@ -95,7 +84,6 @@ impl TranscriptReflowState {
kind: TranscriptReflowKind,
target_width: Option<u16>,
) -> bool {
debug_assert!(!self.is_runtime_disabled());
let now = Instant::now();
if matches!(kind, TranscriptReflowKind::Full) {
self.pending_full_reflow_width = target_width;
@@ -110,7 +98,6 @@ impl TranscriptReflowState {
/// This is used after stream consolidation when waiting for the debounce interval would leave
/// visible terminal-wrapped stream rows in the finalized transcript.
pub(crate) fn schedule_immediate(&mut self, kind: TranscriptReflowKind) {
debug_assert!(!self.is_runtime_disabled());
if matches!(kind, TranscriptReflowKind::Full) {
self.pending_full_reflow_width = None;
}
@@ -163,28 +150,10 @@ impl TranscriptReflowState {
self.last_reflow_width.replace(width) != Some(width)
}
pub(crate) fn clear_runtime_disable(&mut self) {
self.runtime_disabled = None;
self.runtime_disable_warning_shown = false;
pub(crate) fn clear_row_cap_trim_warning(&mut self) {
self.row_cap_trim_warning_shown = false;
}
pub(crate) fn is_runtime_disabled(&self) -> bool {
self.runtime_disabled.is_some()
}
pub(crate) fn runtime_disabled_reason(&self) -> Option<ResizeReflowDisableReason> {
self.runtime_disabled
}
pub(crate) fn take_runtime_disable_warning_needed(&mut self) -> bool {
if self.runtime_disabled.is_none() || self.runtime_disable_warning_shown {
return false;
}
self.runtime_disable_warning_shown = true;
true
}
pub(crate) fn take_row_cap_trim_warning_needed(&mut self) -> bool {
if self.row_cap_trim_warning_shown {
return false;
@@ -193,22 +162,6 @@ impl TranscriptReflowState {
true
}
pub(crate) fn record_elapsed(
&mut self,
reason: ResizeReflowDisableReason,
elapsed: Duration,
threshold: Duration,
) -> bool {
if elapsed <= threshold || self.runtime_disabled.is_some() {
return false;
}
self.runtime_disabled = Some(reason);
self.clear_pending_reflow();
self.clear_stream_flags();
true
}
/// Remember that a reflow actually rebuilt history before stream consolidation completed.
///
/// A mid-stream rebuild can only render the transient stream cells that exist at that moment.
@@ -405,69 +358,6 @@ mod tests {
assert!(!state.take_stream_finish_reflow_needed());
}
#[test]
fn below_threshold_measurement_keeps_reflow_active() {
let mut state = TranscriptReflowState::default();
assert!(!state.record_elapsed(
ResizeReflowDisableReason::RenderSlow,
Duration::from_millis(/*millis*/ 249),
Duration::from_millis(/*millis*/ 250),
));
assert!(!state.is_runtime_disabled());
assert_eq!(state.runtime_disabled_reason(), None);
}
#[test]
fn slow_render_measurement_disables_reflow() {
let mut state = TranscriptReflowState::default();
assert!(state.record_elapsed(
ResizeReflowDisableReason::RenderSlow,
Duration::from_millis(/*millis*/ 251),
Duration::from_millis(/*millis*/ 250),
));
assert!(state.is_runtime_disabled());
assert_eq!(
state.runtime_disabled_reason(),
Some(ResizeReflowDisableReason::RenderSlow)
);
}
#[test]
fn runtime_disable_warning_is_reported_once() {
let mut state = TranscriptReflowState::default();
assert!(!state.take_runtime_disable_warning_needed());
assert!(state.record_elapsed(
ResizeReflowDisableReason::RenderSlow,
Duration::from_millis(/*millis*/ 251),
Duration::from_millis(/*millis*/ 250),
));
assert!(state.take_runtime_disable_warning_needed());
assert!(!state.take_runtime_disable_warning_needed());
}
#[test]
fn slow_flush_measurement_disables_reflow() {
let mut state = TranscriptReflowState::default();
assert!(state.record_elapsed(
ResizeReflowDisableReason::FlushSlow,
Duration::from_millis(/*millis*/ 251),
Duration::from_millis(/*millis*/ 250),
));
assert!(state.is_runtime_disabled());
assert_eq!(
state.runtime_disabled_reason(),
Some(ResizeReflowDisableReason::FlushSlow)
);
}
#[test]
fn row_cap_trim_warning_is_reported_once() {
let mut state = TranscriptReflowState::default();
@@ -477,52 +367,12 @@ mod tests {
}
#[test]
fn clear_resets_runtime_disable() {
fn clear_row_cap_trim_warning_allows_reporting_again() {
let mut state = TranscriptReflowState::default();
state.record_elapsed(
ResizeReflowDisableReason::RenderSlow,
Duration::from_millis(/*millis*/ 251),
Duration::from_millis(/*millis*/ 250),
);
state.clear();
assert!(state.take_row_cap_trim_warning_needed());
state.clear_row_cap_trim_warning();
assert!(!state.is_runtime_disabled());
assert_eq!(state.runtime_disabled_reason(), None);
}
#[test]
fn clear_runtime_disable_preserves_pending_reflow() {
let mut state = TranscriptReflowState::default();
state.record_elapsed(
ResizeReflowDisableReason::RenderSlow,
Duration::from_millis(/*millis*/ 251),
Duration::from_millis(/*millis*/ 250),
);
state.pending_until = Some(Instant::now());
state.pending_kind = Some(TranscriptReflowKind::Full);
state.clear_runtime_disable();
assert!(!state.is_runtime_disabled());
assert!(state.has_pending_reflow());
}
#[test]
fn clear_pending_reflow_preserves_runtime_disable() {
let mut state = TranscriptReflowState::default();
state.record_elapsed(
ResizeReflowDisableReason::RenderSlow,
Duration::from_millis(/*millis*/ 251),
Duration::from_millis(/*millis*/ 250),
);
state.clear_pending_reflow();
assert!(state.is_runtime_disabled());
assert_eq!(
state.runtime_disabled_reason(),
Some(ResizeReflowDisableReason::RenderSlow)
);
assert!(state.take_row_cap_trim_warning_needed());
}
}
-5
View File
@@ -487,8 +487,6 @@ pub(crate) struct ResizeReflowDrawStats {
pub(crate) history_flush_elapsed: Duration,
pub(crate) history_flush_line_count: usize,
pub(crate) flushed_history: bool,
pub(crate) reflow_flush_elapsed: Duration,
pub(crate) flushed_reflow_history: bool,
}
impl Tui {
@@ -947,13 +945,10 @@ impl Tui {
stats.history_flush_line_count = history_flush_line_count;
stats.flushed_history = history_flush_line_count > 0;
needs_full_repaint |= flushed_history;
let reflow_flush_start = Instant::now();
let flushed_reflow_history = Self::flush_pending_reflow_history_lines(
terminal,
&mut self.pending_reflow_history_lines,
)?;
stats.reflow_flush_elapsed = reflow_flush_start.elapsed();
stats.flushed_reflow_history = flushed_reflow_history;
needs_full_repaint |= flushed_reflow_history;
if needs_full_repaint {