Skip to Content
内部原理Viewport 与历史

Viewport 与历史

在终端里,应用当前可见的区域本身就是一个需要被管理的资源。

Ansiq 不把它视作普通 widget 的副作用,而是把它视作 surface/runtime 的职责。

NotesApp 过渡到 transcript 型应用

前面的 NotesApp 还是一个表单 + 列表应用。
如果我们继续把它扩成 transcript / chat / agent workbench,问题会马上变化:

  • 已完成内容要不要继续留在 live 区域
  • 什么时候应该提交到 scrollback
  • viewport 高度在 commit 后该不该缩回去
  • resize 之后历史内容是否要 reflow
  • 退出后 shell prompt 应该落在哪里

这也是为什么 Ansiq 必须把 viewport/history 单独抽出来讲。

完成内容如何离开 live viewport
1当前 live 内容
2判断是否已经完成
3未完成则继续留在 live viewport
4已完成则 commit 到 scrollback
5viewport reanchor
6按 policy 调整 live 高度

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 的历史不会重新换行。
这是当前模型的显式取舍。

TextBlock 走同一条 commit 路径

现在纯文本历史和 block 历史已经统一,不再出现一类按提交时 wrap、另一类按当前 viewport 临时 wrap 的不一致行为。

ReservePreferred 会在 commit 后回到 preferred height

对于 shell / transcript 场景,这一点非常重要。
否则 live viewport 会越长越高,最后把整个 terminal 吃满。

这页真正想让你记住什么

写 Ansiq transcript 应用时,最稳的模式通常是:

  1. 当前这轮内容留在 draft
  2. 流式过程持续更新 draft
  3. 完成后调用 commit_history(...)commit_history_block(...)
  4. 清空 draft
  5. 让 live viewport 回到下一轮的工作状态

一个有用的心智模型

把它理解成:

  • live viewport = 当前工作区
  • scrollback history = 已完成内容

对于简单的 NotesApp,这个边界可能还不明显。
但一旦应用转向 transcript / shell,它就会成为核心设计问题。

最有用的心智模型
1live viewport = 当前工作区
2用户正在交互的内容留在 live 区
3completed turns / logs 进入 scrollback history
Last updated on