use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use serde_json::Value;
use supercode::frontend::FrontendEvent;
use supercode::ChatMessage;
use supercode_frontend_tui::terminal::palette::ColorCapabilities;
use supercode_frontend_tui::terminal::palette::ColorLevel;
use supercode_frontend_tui::transcript::CellState;
use supercode_frontend_tui::transcript::TranscriptKind;
use supercode_frontend_tui::transcript::TranscriptModel;
use supercode_frontend_tui::transcript::TranscriptRenderer;
fn fixture() -> (u64, Vec<ChatMessage>, Vec<FrontendEvent>) {
let value: Value =
serde_json::from_str(include_str!("fixtures/late_attach.json")).expect("valid fixture");
let cursor = value["history_cursor"].as_u64().expect("history cursor");
let history = serde_json::from_value(value["history"].clone()).expect("history messages");
let events = serde_json::from_value(value["events"].clone()).expect("frontend events");
(cursor, history, events)
}
fn capabilities() -> ColorCapabilities {
ColorCapabilities {
level: ColorLevel::TrueColor,
color_enabled: true,
}
}
fn snapshot(model: &TranscriptModel, width: u16) -> String {
let area = Rect::new(0, 0, width, 40);
let mut buffer = Buffer::empty(area);
TranscriptRenderer::new(model, capabilities()).render_buffer(area, &mut buffer);
(0..area.height)
.map(|y| {
(0..area.width)
.map(|x| buffer[(x, y)].symbol())
.collect::<String>()
.trim_end()
.to_string()
})
.collect::<Vec<_>>()
.join("\n")
.trim_end()
.to_string()
}
#[test]
fn recorded_late_attach_replays_then_streams_without_duplicates_across_resizes() {
let (cursor, history, events) = fixture();
let mut model = TranscriptModel::from_history(&history, cursor);
let attached = snapshot(&model, 80);
assert_eq!(
attached,
"✓ You\n Continue the migration without repeating the prior answer.\n\n✓ Assistant\n Earlier context is loaded and the source session remains unchanged."
);
for event in &events[..4] {
model.apply_event(event);
}
let first_resize = snapshot(&model, 40);
assert_eq!(
first_resize,
"✓ You\n Continue the migration without\n repeating the prior answer.\n\n✓ Assistant\n Earlier context is loaded and the\n source session remains unchanged.\n\n✓ Command: exec_command\n 171 tests passed\n\n◌ Assistant\n ## Result\n The attach boundary keeps one\n canonical transcript while live output\n arrives.\n\n • history is replayed once\n • streaming updates one assistant cell\n\n │ Check │ State │\n ├──────────────────┼─────────────────┤\n │ source session │ unchanged │\n │ live stream │ attached │"
);
for event in &events[4..] {
model.apply_event(event);
}
let second_resize = snapshot(&model, 64);
assert_eq!(
second_resize,
"✓ You\n Continue the migration without repeating the prior answer.\n\n✓ Assistant\n Earlier context is loaded and the source session remains\n unchanged.\n\n✓ Command: exec_command\n 171 tests passed\n\n✓ Assistant\n ## Result\n The attach boundary keeps one canonical transcript while live\n output arrives.\n\n • history is replayed once\n • streaming updates one assistant cell\n\n │ Check │ State │\n ├──────────────────────────────┼─────────────────────────────┤\n │ source session │ unchanged │\n │ live stream │ attached │\n\n ┌─ rust\n │ fn resume_losslessly() -> bool { true }\n └─\n The final answer is deliberately long enough to reflow when\n the terminal width changes a second time."
);
assert_eq!(
model
.cells()
.iter()
.filter(|cell| cell.kind == TranscriptKind::User)
.count(),
1,
"the event at the canonical cursor must not duplicate the prompt"
);
assert_eq!(
model
.cells()
.iter()
.filter(|cell| cell.kind == TranscriptKind::Assistant)
.count(),
2,
"one replayed answer plus one live answer"
);
assert_eq!(model.cells().last().unwrap().state, CellState::Complete);
}