use std::time::{Duration, Instant};
use crate::{emulator::Emulator, harness::summary::SummaryAdapter};
pub const TITLE_MIN_HOLD: Duration = Duration::from_millis(500);
pub const DEMOTION_HOLD: Duration = Duration::from_millis(600);
pub const MARKER: &str = "full-screen";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PreviewSource {
Floor,
Marker,
Title,
Anchor,
}
impl PreviewSource {
pub fn label(self) -> &'static str {
match self {
PreviewSource::Floor => "floor",
PreviewSource::Marker => "marker",
PreviewSource::Title => "title",
PreviewSource::Anchor => "anchor",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Preview {
pub text: String,
pub source: PreviewSource,
pub rule: Option<&'static str>,
pub frozen: bool,
}
impl Preview {
fn floor(text: String) -> Preview {
Preview {
text,
source: PreviewSource::Floor,
rule: None,
frozen: false,
}
}
}
pub trait ScreenFacts {
fn revision(&self) -> u64;
fn alt_epoch(&self) -> u64;
fn alternate_screen(&self) -> bool;
fn title(&self) -> Option<&str>;
fn live_floor(&self) -> String;
fn live_rows(&self) -> Vec<String>;
fn alt_leave_floor(&self) -> Option<&str>;
}
impl ScreenFacts for Emulator {
fn revision(&self) -> u64 {
Emulator::revision(self)
}
fn alt_epoch(&self) -> u64 {
Emulator::alt_epoch(self)
}
fn alternate_screen(&self) -> bool {
Emulator::alternate_screen(self)
}
fn title(&self) -> Option<&str> {
Emulator::title(self)
}
fn live_floor(&self) -> String {
Emulator::live_floor(self)
}
fn live_rows(&self) -> Vec<String> {
Emulator::live_rows(self)
}
fn alt_leave_floor(&self) -> Option<&str> {
Emulator::alt_leave_floor(self)
}
}
fn cascade(screen: &impl ScreenFacts, adapter: Option<&dyn SummaryAdapter>) -> Preview {
if let Some(a) = adapter
&& let Some((text, rule)) = a.live_preview(screen)
{
let text = match a.model_label(screen) {
Some(label) => format!("{label} · {text}"),
None => text,
};
return Preview {
text,
source: PreviewSource::Anchor,
rule: Some(rule),
frozen: false,
};
}
if screen.alternate_screen() {
return match screen.title() {
Some(text) => Preview {
text: adapter
.and_then(|a| a.normalize_title(text))
.unwrap_or_else(|| text.to_string()),
source: PreviewSource::Title,
rule: None,
frozen: false,
},
None => Preview {
text: MARKER.to_string(),
source: PreviewSource::Marker,
rule: None,
frozen: false,
},
};
}
let floor = screen.live_floor();
let trimmed = floor.trim_start();
Preview::floor(if trimmed.len() == floor.len() {
floor
} else {
trimmed.to_string()
})
}
type ResolveKey = (u64, u64, bool, Option<String>, bool);
#[derive(Debug)]
pub struct PreviewState {
rendered: Preview,
candidate: Preview,
pending_candidate: Option<Preview>,
downgrade_pending_since: Option<Instant>,
last_title_render: Option<Instant>,
last_key: Option<ResolveKey>,
rendered_under_alt: bool,
finalized: bool,
}
impl Default for PreviewState {
fn default() -> PreviewState {
PreviewState::new()
}
}
impl PreviewState {
pub fn new() -> PreviewState {
let empty = Preview::floor(String::new());
PreviewState {
rendered: empty.clone(),
candidate: empty,
pending_candidate: None,
downgrade_pending_since: None,
last_title_render: None,
last_key: None,
rendered_under_alt: false,
finalized: false,
}
}
pub fn finalized(&self) -> bool {
self.finalized
}
pub fn resolve(
&mut self,
now: Instant,
finished: bool,
screen: &impl ScreenFacts,
adapter: Option<&dyn SummaryAdapter>,
) -> &Preview {
if self.finalized {
return &self.rendered;
}
let key = (
screen.revision(),
screen.alt_epoch(),
screen.alternate_screen(),
screen.title().map(str::to_owned),
finished,
);
if self.last_key.as_ref() != Some(&key) {
self.candidate = cascade(screen, adapter);
self.last_key = Some(key);
}
self.step(now, screen.alternate_screen());
&self.rendered
}
fn step(&mut self, now: Instant, alt: bool) {
use std::cmp::Ordering;
match self.candidate.source.cmp(&self.rendered.source) {
Ordering::Greater => {
self.cancel_demotion();
let cand = self.candidate.clone();
self.render(cand, now, alt);
}
Ordering::Equal => {
self.cancel_demotion();
if self.candidate == self.rendered {
return;
}
match self.candidate.source {
PreviewSource::Marker => {}
PreviewSource::Title => {
let held = self
.last_title_render
.is_some_and(|t| now.duration_since(t) < TITLE_MIN_HOLD);
if !held {
let cand = self.candidate.clone();
self.render(cand, now, alt);
}
}
PreviewSource::Floor | PreviewSource::Anchor => {
let cand = self.candidate.clone();
self.render(cand, now, alt);
}
}
}
Ordering::Less => {
let since = *self.downgrade_pending_since.get_or_insert(now);
self.pending_candidate = Some(self.candidate.clone());
if now.duration_since(since) >= DEMOTION_HOLD
&& let Some(latest) = self.pending_candidate.take()
{
self.downgrade_pending_since = None;
self.render(latest, now, alt);
}
}
}
}
fn render(&mut self, p: Preview, now: Instant, alt: bool) {
if p.source == PreviewSource::Title {
self.last_title_render = Some(now);
}
self.rendered = p;
self.rendered_under_alt = alt;
}
fn cancel_demotion(&mut self) {
self.pending_candidate = None;
self.downgrade_pending_since = None;
}
pub fn finalize(
&mut self,
screen: &impl ScreenFacts,
adapter: Option<&dyn SummaryAdapter>,
exit_line: Option<String>,
) {
if self.finalized {
return;
}
self.finalized = true;
self.cancel_demotion();
if let Some(text) = exit_line {
self.rendered = Preview {
text,
source: PreviewSource::Anchor,
rule: None,
frozen: true,
};
return;
}
let alt_torn_down_at_exit = self.rendered_under_alt
&& !screen.alternate_screen()
&& screen
.alt_leave_floor()
.is_some_and(|snapshot| screen.live_floor() == snapshot);
if alt_torn_down_at_exit {
self.rendered.frozen = true;
return;
}
let mut fin = cascade(screen, adapter);
fin.frozen = true;
self.rendered = fin;
}
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use super::*;
struct FakeScreen {
revision: u64,
alt_epoch: u64,
alt: bool,
title: Option<String>,
floor: String,
alt_leave_floor: Option<String>,
floor_calls: Cell<usize>,
}
impl FakeScreen {
fn primary(floor: &str) -> FakeScreen {
FakeScreen {
revision: 1,
alt_epoch: 0,
alt: false,
title: None,
floor: floor.into(),
alt_leave_floor: None,
floor_calls: Cell::new(0),
}
}
fn advance(&mut self) {
self.revision += 1;
}
fn enter_alt(&mut self) {
self.alt = true;
self.alt_epoch += 1;
self.advance();
}
fn leave_alt(&mut self) {
self.alt = false;
self.alt_leave_floor = Some(self.floor.clone());
self.advance();
}
fn set_title(&mut self, t: &str) {
self.title = Some(t.into());
self.advance();
}
fn clear_title(&mut self) {
self.title = None;
self.advance();
}
fn set_floor(&mut self, f: &str) {
self.floor = f.into();
self.advance();
}
}
impl ScreenFacts for FakeScreen {
fn revision(&self) -> u64 {
self.revision
}
fn alt_epoch(&self) -> u64 {
self.alt_epoch
}
fn alternate_screen(&self) -> bool {
self.alt
}
fn title(&self) -> Option<&str> {
self.title.as_deref()
}
fn live_floor(&self) -> String {
self.floor_calls.set(self.floor_calls.get() + 1);
self.floor.clone()
}
fn live_rows(&self) -> Vec<String> {
vec![self.floor.clone()]
}
fn alt_leave_floor(&self) -> Option<&str> {
self.alt_leave_floor.as_deref()
}
}
struct StubAdapter {
live: Option<(&'static str, &'static str)>,
label: Option<&'static str>,
}
impl SummaryAdapter for StubAdapter {
fn live_preview(&self, _screen: &dyn ScreenFacts) -> Option<(String, &'static str)> {
self.live.map(|(text, rule)| (text.to_string(), rule))
}
fn model_label(&self, _screen: &dyn ScreenFacts) -> Option<String> {
self.label.map(str::to_string)
}
}
#[test]
fn anchor_outranks_title_and_prepends_the_label() {
let now = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("shell");
s.enter_alt();
s.set_title("app");
let adapter = StubAdapter {
live: Some(("Working", "stub:working")),
label: Some("model-x"),
};
let p = st.resolve(now, false, &s, Some(&adapter)).clone();
assert_eq!(
(p.text.as_str(), p.source, p.rule),
(
"model-x · Working",
PreviewSource::Anchor,
Some("stub:working")
)
);
let bare = StubAdapter {
live: Some(("Working", "stub:working")),
label: None,
};
let mut st = PreviewState::new();
let p = st.resolve(now, false, &s, Some(&bare)).clone();
assert_eq!(p.text, "Working");
}
#[test]
fn anchor_loss_demotes_through_the_hold() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("shell");
s.enter_alt();
s.set_title("app");
let working = StubAdapter {
live: Some(("Working", "stub:working")),
label: None,
};
st.resolve(t0, false, &s, Some(&working));
let idle = StubAdapter {
live: None,
label: None,
};
s.advance();
assert_eq!(
st.resolve(t0, false, &s, Some(&idle)).source,
PreviewSource::Anchor,
"a lost anchor must not demote instantly"
);
let p = st
.resolve(t0 + DEMOTION_HOLD, false, &s, Some(&idle))
.clone();
assert_eq!((p.text.as_str(), p.source), ("app", PreviewSource::Title));
}
#[test]
fn finalize_freezes_the_final_anchor() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("building");
let adapter = StubAdapter {
live: Some(("Ran echo ok", "stub:ran")),
label: None,
};
st.resolve(t0, false, &s, None);
s.advance();
st.finalize(&s, Some(&adapter), None);
let p = st.resolve(t0, true, &s, Some(&adapter)).clone();
assert_eq!(
(p.text.as_str(), p.source, p.rule, p.frozen),
("Ran echo ok", PreviewSource::Anchor, Some("stub:ran"), true)
);
}
#[test]
fn finalize_prefers_an_adapter_exit_line() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let s = FakeScreen::primary("junk floor");
st.resolve(t0, false, &s, None);
st.finalize(&s, None, Some("session done".to_string()));
let p = st.resolve(t0, true, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("session done", PreviewSource::Anchor, true)
);
}
#[test]
fn cascade_maps_screen_facts_to_sources() {
let now = Instant::now();
let mut st = PreviewState::new();
let s = FakeScreen::primary("last row");
let p = st.resolve(now, false, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("last row", PreviewSource::Floor, false)
);
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("shell");
s.enter_alt();
s.set_title("app");
let p = st.resolve(now, false, &s, None).clone();
assert_eq!((p.text.as_str(), p.source), ("app", PreviewSource::Title));
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("shell");
s.enter_alt();
let p = st.resolve(now, false, &s, None).clone();
assert_eq!((p.text.as_str(), p.source), (MARKER, PreviewSource::Marker));
let mut st = PreviewState::new();
let s = FakeScreen::primary("");
let p = st.resolve(now, false, &s, None).clone();
assert_eq!((p.text.as_str(), p.source), ("", PreviewSource::Floor));
}
#[test]
fn promotion_renders_immediately() {
let now = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("building");
assert_eq!(
st.resolve(now, false, &s, None).source,
PreviewSource::Floor
);
s.enter_alt();
let p = st.resolve(now, false, &s, None).clone();
assert_eq!((p.text.as_str(), p.source), (MARKER, PreviewSource::Marker));
s.set_title("app");
let p = st.resolve(now, false, &s, None).clone();
assert_eq!((p.text.as_str(), p.source), ("app", PreviewSource::Title));
}
#[test]
fn demotion_commits_only_after_the_hold() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("shell");
s.enter_alt();
s.set_title("app");
st.resolve(t0, false, &s, None);
s.clear_title();
assert_eq!(
st.resolve(t0, false, &s, None).source,
PreviewSource::Title,
"a demotion must not render instantly"
);
let inside = t0 + DEMOTION_HOLD - Duration::from_millis(1);
assert_eq!(
st.resolve(inside, false, &s, None).source,
PreviewSource::Title
);
let p = st.resolve(t0 + DEMOTION_HOLD, false, &s, None).clone();
assert_eq!((p.text.as_str(), p.source), (MARKER, PreviewSource::Marker));
}
#[test]
fn rank_recovery_inside_the_hold_cancels_pending() {
let t0 = Instant::now();
let ms = Duration::from_millis;
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("shell");
s.enter_alt();
s.set_title("app");
st.resolve(t0, false, &s, None);
s.clear_title();
st.resolve(t0, false, &s, None);
s.set_title("app");
let p = st.resolve(t0 + ms(300), false, &s, None).clone();
assert_eq!((p.text.as_str(), p.source), ("app", PreviewSource::Title));
s.clear_title();
st.resolve(t0 + ms(400), false, &s, None);
assert_eq!(
st.resolve(t0 + ms(900), false, &s, None).source,
PreviewSource::Title,
"the canceled hold must not shorten the fresh one"
);
assert_eq!(
st.resolve(t0 + ms(1_000), false, &s, None).source,
PreviewSource::Marker
);
}
#[test]
fn flapping_pending_keeps_the_timer_and_commits_the_latest() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("shell");
s.enter_alt();
s.set_title("app");
st.resolve(t0, false, &s, None);
s.clear_title();
st.resolve(t0, false, &s, None);
s.leave_alt();
s.set_floor("done 3 tests");
assert_eq!(
st.resolve(t0 + Duration::from_millis(300), false, &s, None)
.source,
PreviewSource::Title
);
let p = st.resolve(t0 + DEMOTION_HOLD, false, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source),
("done 3 tests", PreviewSource::Floor),
"the latest pending candidate commits, not the first"
);
}
#[test]
fn title_changes_render_at_most_once_per_min_hold() {
let t0 = Instant::now();
let ms = Duration::from_millis;
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("shell");
s.enter_alt();
s.set_title("one");
assert_eq!(st.resolve(t0, false, &s, None).text, "one");
s.set_title("two");
assert_eq!(st.resolve(t0 + ms(200), false, &s, None).text, "one");
s.set_title("three");
assert_eq!(st.resolve(t0 + ms(300), false, &s, None).text, "one");
assert_eq!(
st.resolve(t0 + TITLE_MIN_HOLD, false, &s, None).text,
"three",
"the newest candidate wins at the deadline"
);
}
#[test]
fn floor_text_renders_immediately() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("compiling foo");
assert_eq!(st.resolve(t0, false, &s, None).text, "compiling foo");
s.set_floor("compiling bar");
assert_eq!(st.resolve(t0, false, &s, None).text, "compiling bar");
}
#[test]
fn unchanged_key_skips_the_candidate_recompute() {
let t0 = Instant::now();
let ms = Duration::from_millis;
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("steady");
st.resolve(t0, false, &s, None);
assert_eq!(s.floor_calls.get(), 1);
st.resolve(t0 + ms(200), false, &s, None);
assert_eq!(s.floor_calls.get(), 1, "unchanged key must not re-read");
s.advance();
st.resolve(t0 + ms(400), false, &s, None);
assert_eq!(s.floor_calls.get(), 2, "a revision bump must recompute");
}
#[test]
fn a_resize_shaped_revision_bump_recomputes_the_candidate() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("a long row that fit");
assert_eq!(st.resolve(t0, false, &s, None).text, "a long row that fit");
s.set_floor("a long row");
assert_eq!(
st.resolve(t0, false, &s, None).text,
"a long row",
"the reflowed floor must render, not the carried candidate"
);
}
#[test]
fn finalize_re_resolves_a_primary_screen() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("running");
st.resolve(t0, false, &s, None);
s.set_floor("test result: ok");
st.finalize(&s, None, None);
let p = st.resolve(t0, true, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("test result: ok", PreviewSource::Floor, true)
);
}
#[test]
fn finalize_keeps_the_rendered_preview_across_alt_teardown() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("prelaunch junk");
s.enter_alt();
s.set_title("agent: working");
st.resolve(t0, false, &s, None);
s.leave_alt();
st.finalize(&s, None, None);
let p = st.resolve(t0, true, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("agent: working", PreviewSource::Title, true)
);
s.set_floor("stray");
assert_eq!(
st.resolve(t0 + Duration::from_secs(5), true, &s, None).text,
"agent: working",
"resolution must short-circuit to the frozen value"
);
}
#[test]
fn finalize_keeps_the_render_when_a_resolve_saw_the_teardown() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("prelaunch junk");
s.enter_alt();
s.set_title("agent: working");
st.resolve(t0, false, &s, None);
s.leave_alt();
let p = st.resolve(t0, false, &s, None).clone();
assert_eq!(
p.source,
PreviewSource::Title,
"premise: the demotion hold keeps the title rendered"
);
st.finalize(&s, None, None);
let p = st.resolve(t0, true, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("agent: working", PreviewSource::Title, true)
);
}
#[test]
fn finalize_trusts_the_screen_after_a_primary_commit() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("prelaunch junk");
s.enter_alt();
s.set_title("agent: working");
st.resolve(t0, false, &s, None);
s.leave_alt();
s.set_floor("wrote 12 files");
st.resolve(t0, false, &s, None);
let p = st.resolve(t0 + DEMOTION_HOLD, false, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source),
("wrote 12 files", PreviewSource::Floor),
"premise: the hold committed the primary floor"
);
s.set_floor("exit summary");
st.finalize(&s, None, None);
let p = st.resolve(t0 + DEMOTION_HOLD, true, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("exit summary", PreviewSource::Floor, true),
"the primary-stamped render must not resurrect the title"
);
}
#[test]
fn finalize_freezes_primary_output_written_after_teardown() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("prelaunch junk");
s.enter_alt();
s.set_title("agent: working");
st.resolve(t0, false, &s, None);
s.leave_alt();
assert_eq!(st.resolve(t0, false, &s, None).source, PreviewSource::Title);
s.set_floor("done");
st.finalize(&s, None, None);
let p = st.resolve(t0, true, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("done", PreviewSource::Floor, true),
"legitimate primary output must not be discarded"
);
}
#[test]
fn finalize_holds_through_control_only_output_after_teardown() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("prelaunch junk");
s.enter_alt();
s.set_title("agent: working");
st.resolve(t0, false, &s, None);
s.leave_alt();
assert_eq!(st.resolve(t0, false, &s, None).source, PreviewSource::Title);
s.clear_title();
assert_eq!(st.resolve(t0, false, &s, None).source, PreviewSource::Title);
st.finalize(&s, None, None);
let p = st.resolve(t0, true, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("agent: working", PreviewSource::Title, true),
"control-only chunks must not defeat the carve-out"
);
}
#[test]
fn finalize_freezes_coalesced_output_after_teardown() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut emu = Emulator::new(24, 80, 100);
emu.process(b"prelaunch junk\r\n");
emu.process(b"\x1b[?1049h\x1b]0;working\x07app body");
assert_eq!(
st.resolve(t0, false, &emu, None).source,
PreviewSource::Title,
"premise: the title rendered under the alt screen"
);
emu.process(b"\x1b[?1049ldone\r\n");
assert_eq!(
emu.alt_leave_floor(),
Some("prelaunch junk"),
"premise: the snapshot is the restore, not the successor line"
);
st.finalize(&emu, None, None);
let p = st.resolve(t0, true, &emu, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("done", PreviewSource::Floor, true)
);
}
#[test]
fn finalize_holds_across_a_resize_after_teardown() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut emu = Emulator::new(24, 40, 100);
emu.process(b"prelaunch junk that will wrap\r\n");
emu.process(b"\x1b[?1049h\x1b]0;working\x07app body");
assert_eq!(
st.resolve(t0, false, &emu, None).source,
PreviewSource::Title
);
emu.process(b"\x1b[?1049l");
let before = emu.live_floor();
emu.resize(24, 20);
assert_ne!(
emu.live_floor(),
before,
"premise: the reflow moved the floor"
);
st.finalize(&emu, None, None);
let p = st.resolve(t0, true, &emu, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("working", PreviewSource::Title, true),
"a reflow is not post-teardown output"
);
}
#[test]
fn finalize_freezes_output_across_a_resize_after_teardown() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut emu = Emulator::new(24, 40, 100);
emu.process(b"prelaunch junk that will wrap\r\n");
emu.process(b"\x1b[?1049h\x1b]0;working\x07app body");
assert_eq!(
st.resolve(t0, false, &emu, None).source,
PreviewSource::Title
);
emu.process(b"\x1b[?1049l");
emu.process(b"done\r\n");
emu.resize(24, 20);
st.finalize(&emu, None, None);
let p = st.resolve(t0, true, &emu, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("done", PreviewSource::Floor, true),
"real post-teardown output must survive the resize"
);
}
#[test]
fn finalize_on_the_alt_screen_freezes_the_final_cascade() {
let t0 = Instant::now();
let mut st = PreviewState::new();
let mut s = FakeScreen::primary("shell");
s.enter_alt();
s.set_title("step 1");
st.resolve(t0, false, &s, None);
s.set_title("step 2: done");
st.finalize(&s, None, None);
let p = st.resolve(t0, true, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source, p.frozen),
("step 2: done", PreviewSource::Title, true)
);
}
#[test]
fn floor_preview_trims_leading_indentation() {
let mut st = PreviewState::new();
let t0 = Instant::now();
let s = FakeScreen::primary(" gpt-5.6-sol high · fleetcom · 89.9K used");
let p = st.resolve(t0, false, &s, None).clone();
assert_eq!(
(p.text.as_str(), p.source),
(
"gpt-5.6-sol high · fleetcom · 89.9K used",
PreviewSource::Floor
)
);
}
}