Viewport and History
In terminal software, the visible app area is itself a managed resource.
Ansiq treats it as part of the surface/runtime model instead of as a random side effect of widget rendering.
Move from NotesApp toward transcript apps
NotesApp starts as a form-and-list app. If you keep evolving it toward a transcript, chat, or agent workbench, the questions change immediately:
- should completed content remain in the live area
- when should content be committed to scrollback
- should viewport height shrink back after commit
- should history reflow after resize
- where should the shell prompt land on exit
That is why viewport and history deserve their own guide page.
What the surface layer handles
The surface layer currently handles:
- raw mode lifecycle
- viewport reservation
- reanchor and resize plans
- history commit into scrollback
- exit row restoration
The smallest useful code path: move completed live content into scrollback
If you already have a streaming draft, the smallest practical path usually looks like this:
use ansiq_runtime::RuntimeHandle;
#[derive(Clone, Debug)]
enum Message {
Chunk(String),
Finish,
}
#[derive(Default)]
struct TranscriptApp {
draft: String,
streaming: bool,
}
fn update(&mut self, message: Message, handle: &RuntimeHandle<Message>) {
match message {
Message::Chunk(chunk) => {
if !self.draft.is_empty() {
self.draft.push('\n');
}
self.draft.push_str(&chunk);
}
Message::Finish => {
let _ = handle.commit_history(self.draft.clone());
self.draft.clear();
self.streaming = false;
}
}
}This is the core transcript split in Ansiq:
draftis the active live viewport contentcommit_history(...)hands completed content to the surface/runtime- the app only decides when that content counts as completed
So history is not a side effect of a widget. It is an explicit runtime/surface operation.
If you need styled history blocks
Plain text can use commit_history(...) directly.
If you need structured or styled history, build a HistoryBlock explicitly:
use ansiq_core::history_block_from_text;
fn commit_draft_as_block(&mut self, handle: &RuntimeHandle<Message>) {
let block = history_block_from_text(&self.draft, 80);
let _ = handle.commit_history_block(block);
self.draft.clear();
}This example stays intentionally small. In a real application you will often build HistoryBlock from a richer transcript model instead of converting from a plain string at the last moment.
Current documented behavior
History wraps at commit time
History content wraps at the width it had when it was committed.
Committed history does not reflow later
If terminal width changes later, committed scrollback history does not reflow. This is an explicit tradeoff of the current model.
Text and Block history now share one path
Plain text history and block history now go through the same commit-time path, so their wrap behavior is consistent.
ReservePreferred returns to preferred height after commit
That matters for shell- and transcript-like applications. Without it, the live viewport would just keep growing until it consumed the entire terminal.
The one thing this page wants you to remember
In Ansiq transcript-style apps, the stable pattern is usually:
- keep the current turn in
draft - keep updating
draftwhile streaming - call
commit_history(...)orcommit_history_block(...)when completed - clear
draft - let the live viewport reset into the next working state
A useful mental model
Think of it like this:
- the live viewport is the current working area
- scrollback history is completed content
For a small app that boundary may be subtle. For transcript-oriented software it becomes central.