Skip to main content
Glance blocks are the vocabulary of result cards, attached at runtime via glanceResult([...]). This page is exhaustive; the renderer silently drops anything off-schema, so treat every cap as hard.

Global rules

  • 1–3 blocks per card; at most one chart (bars, line, splitBar) — extras are dropped, not truncated.
  • Every string is trimmed and must be non-empty; every number must be finite.
  • tone is always "neutral" | "good" | "bad" — a meaning, not a color. The renderer owns hues.
  • Glance cards render on the notch’s dark glass; there is no light variant on this surface.
  • One failing block never sinks the card (per-block error boundaries), and one invalid block never invalidates its siblings — it’s dropped and counted.
appIcon renders a real app logo (e.g. "notion") — but only when the visible title starts with that app’s name; otherwise the slot is dropped so one brand can’t wear another’s icon.

stats

Wraps to a second line before truncating.

bars chart

Every bar carries its value label; the series switches to compact form (16K, 1.2M) when exact numbers would collide.

line chart

The latest value is always called out.

splitBar chart

Segment colors are assigned by index (blue → teal → purple → neutral) — never by the author, and never green/red, so composition can’t be confused with good/bad. Sliver segments are floored at ~2% so they stay visible.

progress

max defaults to 100, not 1 — { "value": 0.4 } renders as 0%. Send { "value": 40 } or set max explicitly.

list

badge and trailing are mutually exclusive at render time — a badge wins. A subtitle that repeats the badge text is dropped automatically.

keyValue

clock live

Ticks every second. An unknown timezone falls back to local time and relabels itself “Local” rather than showing a wrong city.

countdown live

Exactly one of until / seconds is required. Prefer until — an absolute target survives remounts. The 30-day cap applies to seconds only; a far-future until renders honestly (168d 04:30). Turns green at zero.

badges

divider

{ "type": "divider" } — no props.

row

Lays children side by side. Rules: rows never nest, widget can’t be a child, and at most one chart per row (it consumes the card’s chart budget). Children size themselves two ways:

widget

The sandboxed escape hatch — see Custom widgets. Not available to the model — only integration tool results can carry one. Cannot sit inside a row.

Glyphs

The 34-name allowlist for every icon prop:
Unknown names invalidate the block. Rendered as stroked template icons that inherit the surrounding ink color.

Parse & degradation semantics

  • The wire format is { "blocks": [...] } under the _voiceos_glance key of your tool’s JSON result; VoiceOS strips it before the model reads the result.
  • Invalid blocks are dropped individually; a card with zero valid blocks is dropped whole.
  • After validation, the first 3 blocks survive and the first chart consumes the chart budget; the rest are dropped.
  • Extraction caps the whole glance payload at 32,000 chars — raised to 96,000 when a widget block is present.