窗口与内容块
app.open(title) 返回一个 Window。通过 win.blocks() 填充它;每个方法都会追加一个内容块,并返回一个类型化句柄,供你日后更新。
内容块类型
| 方法 | 句柄 | 适用于 |
|---|---|---|
fields([(label, value), …]) | FieldsHandle | 带标签的键值状态——表单、详情面板 |
table(columns, rows) | TableHandle | 结构一致的记录 |
list(items) / ordered_list | ListHandle | 序列 |
text(s) | TextHandle | 说明文字、概述 |
notice(severity, text) | NoticeHandle | 校验与状态提示 |
media(mime, url) | MediaHandle | 图片、图表、截图 |
custom(payload) | BlockHandle | 任何实现了 Serialize 且不属于其他类型的数据 |
rust
let win = app.open("Inbox");
win.set_summary("12 unread");
let stats = win.blocks().fields([("Unread", 12), ("Total", 340)]);
let mail = win.blocks().table(["From", "Subject"], vec![vec!["ops", "Deploy done"]]);
let banner = win.blocks().notice(Severity::Info, "Syncing…");让投影保持最新
句柄的每个方法都是一次投影更新:版本号递增和客户端通知都是自动完成的。在你的 UI 变更事件里调用它们即可。
rust
fn on_recipient_changed(form: &FieldsHandle, win: &Window, value: &str) {
form.set("Recipient", value); // any Serialize value; auto version++ + notify
win.set_summary(if value.is_empty() { "…recipient empty" } else { "…ready" });
}单一同步入口的习惯
只要不是单个标量那么简单,就写一个方法把当前状态整体镜像过去,并在每次变更后调用它。这正是让失真无从发生的关键:
rust
impl Todos {
/// The only place the projection is written.
fn sync(&self) {
self.table.clear_rows();
for (i, t) in self.items.iter().enumerate() {
self.table.push_row([
(i + 1).to_string(),
t.text.clone(),
if t.done { "done" } else { "open" }.to_string(),
]);
}
}
/// Mutate, then sync — the one path buttons and agent actions share.
fn mutate(&mut self, f: impl FnOnce(&mut Self)) {
f(self);
self.sync();
}
}字段是 upsert 语义
当标签不存在时,FieldsHandle::set 会追加一个新字段,而不是报错。这让投影可以动态地增加字段——但也意味着写错标签时只会悄悄多出一个字段,而不会失败。
大型集合
不要把上千行数据全部投影出去。只镜像其中一页,并用 table_truncated / list_truncated 声明该窗口,好让 Agent 知道自己看到的只是一个切片:
rust
// rows[offset .. offset+PAGE] out of `total` overall
let log = win.blocks().table_truncated(["Seq", "Kind"], page_rows, offset, total);
// Later, after the underlying data changed:
log.set_truncation(new_offset, new_total);注意
push_row 不会自动调整 total。对于已截断的内容块,只要真实总数发生变化,就要用 set_truncation 重新声明它。
敏感数据
只需标记一次;此后你像写普通字段一样写它,Purview 会在输出时自动脱敏:
rust
let f = win.blocks().fields([("Account", "")]);
f.set_sensitive("Account", true);
f.set("Account", "12345678"); // mirrored, but never exposed in app://windows不可逆
清除该标记并不会恢复原文——之后需要重新写入一个新值。