This document describes the sanitized widget manifest contract used by the Canopy Deck when a post or message contains embeds, stream cards, or other typed deck items. It is aimed at integrators and future feature work (e.g. station-style surfaces), not end users.
For end-user behavior, see QUICKSTART.md (rich links and media deck) and API_REFERENCE.md (rich media notes).
- Safety: Only allowlisted iframe hosts, external hosts, and callback handlers are accepted.
- Consistency: Every widget gets normalized
station_surface,action_policy, andsource_bindingeven if the producer omits them. - Trust: The deck can show what kind of operational surface the user is in (policy pill + badges) without implying broader permissions than the client actually grants.
- Producers attach JSON to DOM nodes, typically via
data-canopy-widget-manifest="..."(e.g. rich embed HTML fromcanopy-main.js, stream cards fromchannels.html). sanitizeDeckWidgetManifest()incanopy/ui/static/js/canopy-main.jsparses and normalizes; invalid manifests are rejected (null).parseDeckWidgetManifest(node)re-sanitizes afterJSON.parsewhen building deck items from the DOM.- Deck UI renders
renderDeckStationSummary, widget badges/details/actions, and optional iframe stage.
| Field | Notes |
|---|---|
version |
Always 1 for this contract. |
key |
Stable string id for selection within a source. |
widget_type |
One of: map, chart, media_embed, story, media_stream, telemetry_panel. |
render_mode |
One of: iframe, card, stream_summary. |
title |
Required; short heading. |
subtitle, body_text, provider_label, icon |
Optional UI copy; icon is Bootstrap Icons class (bi-*). |
embed_url, external_url, thumb_url |
URLs re-validated against allowlists. |
badges, details |
Bounded arrays for chip and key/value rows. |
station_surface |
See below. |
action_policy |
See below. |
source_binding |
See below. |
actions |
Bounded list (max 4) of allowed actions. |
| Field | Type | Description |
|---|---|---|
kind |
enum | source_bundle, reference_surface, stream_station, telemetry_station, station_surface. Unknown values fall back to defaults per widget_type. |
domain |
enum | e.g. media, sensor, mapping, market, general, … |
label |
string | Primary station line in the deck (e.g. “Live video Surface”). |
summary |
string | Subtitle explaining context. |
recurring |
bool | true for live/recurring operational surfaces (e.g. streams). |
scope |
source | station |
Whether the surface is framed as tied to the message/post vs a broader station context. |
Defaults: If the producer omits station_surface, defaults are chosen from widget_type (e.g. maps → reference_surface / mapping; streams → stream_station or telemetry_station).
Deck UI note: For a simple reference surface (kind: reference_surface, not recurring, scope: source, max_risk: view, human_gate: none), the web UI may omit the separate Station Surface summary block to avoid repeating context already obvious from the map/chart stage. The manifest fields are still normalized and used elsewhere (e.g. policy). Streams and station-scoped surfaces always show the summary when relevant.
| Field | Type | Description |
|---|---|---|
bounded |
bool | Intended for future policy toggles; defaults true. |
max_risk |
view | low |
Ceiling for actions: if view, only risk: view actions are kept. |
human_gate |
none | recommended | required |
Shown as a badge when not none (UX hint for future flows). |
audit_label |
string | Short label for the policy pill (e.g. “Bounded actions”, “View-only actions”). |
| Field | Type | Description |
|---|---|---|
binding_type |
string | Opaque type label (e.g. message_attachment). |
source_scope |
source | station |
Normalized scope. |
return_label |
string | Text for the deck Return control (default “Return to source”). |
Each action object may include:
kind:external_link|clipboard|callbacklabel, optionaliconrisk:view|lowscope:source|stationrequires_confirmation: optional bool → browser confirm before run
external_link: url must pass CANOPY_DECK_EXTERNAL_HOSTS.
clipboard: text length bounded.
callback: handler must be in the allowlist (open_stream_workspace today). Args are validated per handler.
Runtime: canRunDeckWidgetAction(action, manifest) enforces action_policy.max_risk before execution.
Channel stream attachment cards in canopy/ui/templates/channels.html emit a full manifest including station_surface, action_policy, source_binding, and mixed actions (workspace callback + copy stream ID).
Use them as the reference when adding new producers.
- Arbitrary HTML/JS widgets from untrusted authors
- New callback handlers without code review and allowlist updates
- Server-side authorization for “station” actions (future work)
canopy/ui/static/js/canopy-main.js— sanitization, embedwidgetManifestbuilderscanopy/ui/templates/base.html— deck shell, station summary blockcanopy/ui/templates/channels.html— stream card manifesttests/test_frontend_regressions.py— string anchors for CI