Pi Daemon publishes a code-free, versioned extension
view contract for Rich Dash panes while preserving stock Pi RPC dialogs
and the shadow-TUI compatibility path. The contract is additive: clients
that do not negotiate extensionViews never receive
extension_view events and render the required plain-text
fallback instead.
The language-neutral schema is extension-view.schema.json.
TypeScript consumers can import
@harryaskham/pi-daemon/extension-view-contract.
An extension remains server-side code in the Pi runtime trust domain. It does not send JavaScript, React components, CSS, HTML, commands, URLs, or callbacks to the browser. The host:
extension_ui_request
whose method is render_view;transport=pi-rpc,
validator=pi-daemon, validation outcome, and
browserCodeExecution=false);Unknown fields, node kinds, protocol versions, ambient links,
URL/data image sources, duplicate identifiers, undeclared actions,
unsafe control characters, and over-limit trees fail closed. Invalid
source content is never forwarded as an untyped browser payload. A
rejected view can expose only its bounded fallbackText, or
a host-generated generic fallback when that text is unsafe.
blobRef is an opaque blob: identifier. A
renderer may resolve it only through an authenticated host-owned blob
resolver. The current Dash renderer shows an authorized-image
placeholder; it never fetches an extension-provided URL.
Every document carries exact protocol/version/revision identity and a fallback:
{
"protocol": "pi-declarative-view",
"version": "1.0",
"viewId": "review-01",
"revision": 2,
"title": "Review changes",
"fallbackText": "Review the changes in the compatible TUI.",
"capabilities": {
"actions": ["continue"],
"links": "none",
"images": "authorized-blob-only"
},
"root": {
"type": "stack",
"children": [
{ "type": "markdown", "text": "## Two files changed" },
{ "type": "action", "actionId": "continue", "label": "Continue", "tone": "primary" }
]
}
}The allowlist is:
text and safe-rendered markdown;code and diff as inert text;image placeholders;key-value tables and semantic
status blocks;stack and up-to-four-column grid
containers;action buttons; andform nodes with text, multiline, select, and boolean
fields.There are deliberately no raw HTML, style, script, iframe, web component, network request, terminal input, arbitrary link, or generic JSON-renderer primitives.
capabilities.actions is a least-authority list scoped to
one exact viewId/revision. Every declared
action must appear exactly once in the tree, and every rendered action
or form submit must be declared. A browser response returns all four
correlation fields:
{
"protocol": "pi-declarative-view",
"version": "1.0",
"viewId": "review-01",
"revision": 2,
"actionId": "continue"
}Optional form values are a bounded map of strings and booleans. Responses for a stale revision or undeclared action are invalid. Dashboard request IDs and subscription/controller identity provide the outer transport correlation; view identity never substitutes for those checks.
Current capability negotiation reports:
Default limits are 256 KiB encoded view size, 256 nodes, depth 16, 128 KiB aggregate text, 32 actions, 32 fields, 128 select options, and 16 images. The validator enforces count, depth, bytes, identifier uniqueness, and per-record limits before replay or browser delivery. The browser additionally keeps at most eight current view requests per live pane.
Pi Daemon does not monkey-patch ExtensionUIContext and
does not reinterpret ctx.ui.custom() component factories.
The intended upstream seam is an additive API such as:
ctx.ui.renderView(document, { signal, timeout }): Promise<ExtensionViewResponse | undefined>RPC mode would carry it as correlated
extension_ui_request(method=render_view) and
extension_ui_response values. Interactive Pi can render the
same allowlisted model or its mandatory fallback. Until that supported
seam lands, current stock dialogs/widgets continue unchanged; the
negotiated contract and Rich renderer are ready for compatible RPC
producers without claiming that arbitrary Pi components are
portable.
Shadow TUI remains the fidelity path for existing interactive components, custom editors, overlays, terminal input, and extension component factories. Declarative views complement it; they do not replace it.