Presentation primitives

Copy Markdown View Source

Presentation primitives add bounded native decoration without moving application state, topology, parsing, or accessibility policy out of Elixir. They are ordinary schema-owned GPUI.UI components and serialize through the same local, test, and remote snapshot path as other elements.

Use these primitives for neutral presentation mechanics:

  • edge_fade/1 decorates the edges around arbitrary child content;
  • frost/1 renders a surface with an explicit solid or translucent fallback;
  • paint/1 renders a bounded rectangle-and-line display list.

They are not native callbacks, shader APIs, plugins, or arbitrary component injection. See Presentation extension contracts for renderer versions, capability advertisement, and generated decoding.

Edge fades

An edge fade wraps arbitrary children. It does not own scrolling, clipping, collection state, or content topology, so place the semantic <scroll> element inside it when the content should scroll:

<UI.edge_fade
  id="activity-fades"
  edges={[:top, :bottom]}
  size={24}
  opacity={0.9}
  class="relative grow min-h-0 rounded-lg bg-slate-900"
>
  <scroll class="w-full h-full p-5">
    <text>Activity content</text>
  </scroll>
</UI.edge_fade>

edges is a unique subset of :top, :right, :bottom, and :left. size is bounded to 1..256 native pixels and opacity to 0.0..1.0. Static container presentation still belongs in Tailwind-compatible classes.

When a display does not implement the optional fade enhancement, the child tree continues to render normally.

Frost surfaces

A frost surface requires an explicit fallback policy:

<UI.frost
  id="summary-frost"
  fallback="translucent"
  opacity={0.86}
  reduced_transparency={assigns.reduced_transparency}
  class="p-4 rounded-lg border border-slate-700"
>
  <text class="font-semibold">Summary</text>
</UI.frost>

fallback is either "solid" or "translucent", and opacity is bounded to 0.0..1.0. The pinned native stack has no element-level backdrop-blur API, so frost/1 does not claim one.

Accessibility policy remains application-owned. Set reduced_transparency from the serialized application policy; when true, it forces an opaque surface even if the ordinary fallback is translucent. A native display must not infer or replace that policy by changing snapshot topology.

Bounded custom paint

paint/1 renders a serializable display list in the bounds established by its classes:

<UI.paint
  id="sparkline"
  commands={[
    %{type: :rect, x: 0, y: 30, width: 220, height: 1, color: 0x334155FF},
    %{type: :line, x1: 8, y1: 28, x2: 210, y2: 6, width: 2, color: 0x38BDF8FF}
  ]}
  class="w-full h-12"
/>

Version 1 accepts only closed :rect and :line command maps:

%{type: :rect, x: x, y: y, width: width, height: height, color: rgba}
%{type: :line, x1: x1, y1: y1, x2: x2, y2: y2, width: width, color: rgba}

Colors are 32-bit RGBA integers. The list is limited to 256 commands; coordinates and dimensions are bounded, rectangle dimensions may be zero, and line widths must be greater than zero. Unknown commands, shader source, callbacks, and opaque payloads are rejected before a snapshot reaches a display.

The canvas fills its styled wrapper. Declare its width and height through classes or other ordinary bounded layout; paint commands do not establish the component's layout size. A display without paint support renders an empty canvas rather than executing an alternative payload.

Fallbacks and remote displays

Presentation support is informational and display-local. It never determines whether a snapshot may mount and never permits a display to add, remove, or rearrange application elements. Fallbacks travel in the serialized component, so local and remote displays receive the same topology and application policy.

Use GPUI.Display.presentation_capabilities/2 only for diagnostics or display introspection. Do not branch application topology on it.

Example and testing

The complete composition example is examples/features/presentation_primitives.exs. Renderer-independent tests can query these components as ordinary elements:

runtime = start_runtime!(Features.PresentationPrimitives.App)

assert %{type: :ui_frost} = runtime |> tree() |> find!(id: "summary-frost")
assert %{type: :ui_edge_fade} = runtime |> tree() |> find!(id: "activity-fades")
assert %{type: :ui_paint} = runtime |> tree() |> find!(id: "sparkline")

Use deterministic native tests for decoder rejection, actual GPUI layout, and native presentation mechanics. Pixel-level readability and appearance remain visual-evidence claims rather than renderer-independent assertions.