Viewport 与历史
在终端里,应用当前可见的区域本身就是一个需要被管理的资源。
Ansiq 不把它视作普通 widget 的副作用,而是把它视作 surface/runtime 的职责。
从 NotesApp 过渡到 transcript 型应用
前面的 NotesApp 还是一个表单 + 列表应用。
如果我们继续把它扩成 transcript / chat / agent workbench,问题会马上变化:
- 已完成内容要不要继续留在 live 区域
- 什么时候应该提交到 scrollback
- viewport 高度在 commit 后该不该缩回去
- resize 之后历史内容是否要 reflow
- 退出后 shell prompt 应该落在哪里
这也是为什么 Ansiq 必须把 viewport/history 单独抽出来讲。
surface 层负责什么
当前 surface 层主要负责:
- raw mode 进入与退出
- viewport reservation
- reanchor / resize plan
- history commit 到 scrollback
- exit row 恢复
最小代码路径:流式输出完成后,把 live 内容提交到 scrollback
如果你已经有一个正在流式更新的 draft,最小可用路径通常长这样:
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;
}
}
}这段代码表达的是 Ansiq 在 transcript 场景里的一个核心分层:
draft是当前 live viewport 里的工作内容commit_history(...)把它交给 surface/runtime,写入 scrollback- app 本身只负责决定“什么时候这段内容算完成了”
也就是说,history 不是一个普通 widget 的副作用,而是一个明确的 runtime/surface 操作。
如果你需要带样式的历史块
纯文本可以直接用 commit_history(...)。
如果你要提交的是带结构、带样式的历史内容,可以显式构造 HistoryBlock:
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();
}文档里这个例子故意保持最小。真实应用里,你通常会把 HistoryBlock 作为一层更正式的 transcript model 来构造,而不是临时从字符串生成。
当前明确的语义
history 在提交时 wrap
历史内容会在提交时按当时宽度 wrap。
已提交历史不会在之后 reflow
如果终端宽度后来变化,已经进入 scrollback 的历史不会重新换行。
这是当前模型的显式取舍。
Text 和 Block 走同一条 commit 路径
现在纯文本历史和 block 历史已经统一,不再出现一类按提交时 wrap、另一类按当前 viewport 临时 wrap 的不一致行为。
ReservePreferred 会在 commit 后回到 preferred height
对于 shell / transcript 场景,这一点非常重要。
否则 live viewport 会越长越高,最后把整个 terminal 吃满。
这页真正想让你记住什么
写 Ansiq transcript 应用时,最稳的模式通常是:
- 当前这轮内容留在
draft - 流式过程持续更新
draft - 完成后调用
commit_history(...)或commit_history_block(...) - 清空
draft - 让 live viewport 回到下一轮的工作状态
一个有用的心智模型
把它理解成:
- live viewport = 当前工作区
- scrollback history = 已完成内容
对于简单的 NotesApp,这个边界可能还不明显。
但一旦应用转向 transcript / shell,它就会成为核心设计问题。