Skip to content

Windows & blocks

app.open(title) returns a Window. Fill it through win.blocks(); each method appends a block and returns a typed handle you keep for later updates.

Block types

MethodHandleUse for
fields([(label, value), …])FieldsHandleLabelled key/value state — forms, detail panes
table(columns, rows)TableHandleUniform records
list(items) / ordered_listListHandleSequences
text(s)TextHandleProse, summaries
notice(severity, text)NoticeHandleValidation and status messages
media(mime, url)MediaHandleImages, charts, screenshots
custom(payload)BlockHandleAnything Serialize that fits no other kind
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…");

Keeping the projection current

Every handle method is a projection update: version bumps and client notifications are automatic. Call them from your UI's change events.

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" });
}

The one-sync habit

For anything more than a single scalar, write one method that mirrors current state and call it after every mutation. This is what makes drift impossible:

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();
    }
}

Fields upsert

FieldsHandle::set appends a field when the label is unknown, rather than erroring. That lets a projection grow fields dynamically — but it also means a typo silently adds a field instead of failing.

Large collections

Do not project thousands of rows. Mirror one page and declare the window with table_truncated / list_truncated, so the agent knows it is seeing a slice:

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);

Careful

push_row does not adjust total. On a truncated block, restate it with set_truncation whenever the real count changes.

Sensitive values

Mark a field once; from then on you write it like any other, and Purview redacts it on the way out:

rust
let f = win.blocks().fields([("Account", "")]);
f.set_sensitive("Account", true);
f.set("Account", "12345678"); // mirrored, but never exposed in app://windows

One-way

Clearing the flag does not restore the plaintext — set a fresh value afterwards.