# `BB.TUI.Renderer`
[🔗](https://github.com/mcass19/bb_tui/blob/v0.3.0/lib/bb/tui/renderer.ex#L1)

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. Return `nil` to fall
    back to `bb_tui`'s generic `inspect/2`.
  * `observed/2` (optional) — feed the at-a-glance status-bar readout. Return a
    `{slot_key, display, meta}` triple to record/refresh a slot, or `nil` to
    skip. The status bar surfaces the freshest slot (max `meta.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 same `slot_key` overwrite, so the
    readout shows the latest value of each slot rather than accumulating.
  * `display` — a `map()` the status bar renders. `bb_tui` reads at least
    `:label` (a `String.t()` shown in the segment) from it; consumers may carry
    extra fields for their own future use.
  * `meta` — a `map()` 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 `:seq` as "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
    end

Then:

    BB.TUI.run(MyApp.Robot,
      subscribe_paths: [[:demo]],
      renderers: %{[:demo] => MyApp.SlotRenderer}
    )

# `display`

```elixir
@type display() :: %{optional(atom()) =&gt; term()}
```

The status-bar display map. `bb_tui` reads at least `:label`.

# `meta`

```elixir
@type meta() :: %{freshness: :fresh | :stale, seq: term()}
```

Slot metadata. Carries at least `:freshness` and a sortable `:seq`.

# `slot_key`

```elixir
@type slot_key() :: term()
```

A registered slot key — any term the renderer uses to identify a slot.

# `observed`
*optional* 

```elixir
@callback observed(path :: [atom()], payload :: term()) ::
  {slot_key(), display(), meta()} | nil
```

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.

# `summarize`

```elixir
@callback summarize(path :: [atom()], payload :: term()) :: String.t() | nil
```

Returns a one-line event-log summary for `payload` on `path`, or `nil` to fall
back to `bb_tui`'s generic `inspect/2` rendering.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
