Behaviour a consumer implements to teach the dashboard how to render a
payload on a PubSub path the consumer owns — without bb_tui knowing anything
about that payload's shape or struct.
bb_tui subscribes to whatever paths it is told (:subscribe_paths) and
dispatches each {:bb, path, msg} by path prefix. Most prefixes have
dedicated, built-in handling ([:state_machine], [:sensor], [:param], …).
Anything else falls to a generic event-log row via inspect/2.
A renderer plugs into that generic seam: a consumer registers a module for a
path prefix via the :renderers option (a %{prefix => module} map threaded
through BB.TUI.run/2, start/2, start_ssh/2). When a message arrives whose
path matches a registered prefix (longest prefix wins, like a routing table),
bb_tui calls back into the consumer's module rather than pattern-matching the
payload itself. The dashboard therefore stays free of any downstream struct
knowledge — the consumer owns the rendering of its own data.
Two callbacks:
summarize/2— the one-line string for the event log. Returnnilto fall back tobb_tui's genericinspect/2.observed/2(optional) — feed the at-a-glance status-bar readout. Return a{slot_key, display, meta}triple to record/refresh a slot, ornilto skip. The status bar surfaces the freshest slot (maxmeta.seq) and dims stale ones (meta.freshness == :stale).
Return shapes
summarize(path, payload) → String.t() | nil.
observed(path, payload) → {slot_key, display, meta} | nil where:
slot_key— any term that identifies the slot (e.g.{:wheels, :imu}or a bare atom). Successive samples on the sameslot_keyoverwrite, so the readout shows the latest value of each slot rather than accumulating.display— amap()the status bar renders.bb_tuireads at least:label(aString.t()shown in the segment) from it; consumers may carry extra fields for their own future use.meta— amap()carrying at least::freshness—:fresh | :stale. Stale slots render dimmed.:seq— a sortable term (typically an integer or timestamp). The status bar picks the slot with the maximum:seqas "freshest".
Example
defmodule MyApp.SlotRenderer do
@behaviour BB.TUI.Renderer
@impl true
def summarize([:demo | _], %{name: name, value: value}) do
"#{name} = #{value}"
end
def summarize(_path, _payload), do: nil
@impl true
def observed([:demo | _], %{name: name, value: value} = p) do
{name, %{label: "#{name}:#{value}"},
%{freshness: Map.get(p, :freshness, :fresh), seq: Map.get(p, :seq, 0)}}
end
def observed(_path, _payload), do: nil
endThen:
BB.TUI.run(MyApp.Robot,
subscribe_paths: [[:demo]],
renderers: %{[:demo] => MyApp.SlotRenderer}
)
Summary
Types
The status-bar display map. bb_tui reads at least :label.
Slot metadata. Carries at least :freshness and a sortable :seq.
A registered slot key — any term the renderer uses to identify a slot.
Types
Callbacks
Returns {slot_key, display, meta} to record/refresh an at-a-glance status-bar
slot, or nil to skip. Optional — a renderer that only needs an event-log row
can omit it.
Returns a one-line event-log summary for payload on path, or nil to fall
back to bb_tui's generic inspect/2 rendering.