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

Events panel — displays a scrollable list of recent robot messages with
formatted timestamps, color-coded paths, message types, and summaries.

Press Enter on a selected event to open a detail popup showing the full
message payload. Supports pause/resume (`p`) and clear (`c`) when focused.

Pure function — takes state, returns a widget struct.

# `event_line`

```elixir
@spec event_line({DateTime.t(), list(), term()}) :: ExRatatui.Text.Line.t()
```

Builds a rich-text `%Line{}` for a single event.

| segment   | color  |
| --------- | ------ |
| timestamp | cyan   |
| path      | blue (`Theme.path_style/0`) |
| summary   | white  |

## Examples

    iex> ts = ~U[2026-01-15 18:23:12.000Z]
    iex> %ExRatatui.Text.Line{spans: spans} =
    ...>   BB.TUI.Panels.Events.event_line(
    ...>     {ts, [:state_machine], %{payload: %{from: :disarmed, to: :armed}}}
    ...>   )
    iex> Enum.map_join(spans, "", & &1.content)
    "18:23:12 state_machine      disarmed → armed"

# `event_line`

```elixir
@spec event_line({DateTime.t(), list(), term()}, BB.TUI.State.t()) ::
  ExRatatui.Text.Line.t()
```

Builds an event `%Line{}`, consulting any consumer renderer registered in
`state` for the event's path before falling back to the built-in `summarize/2`
clauses. The dashboard never inspects the payload's struct: a renderer-owned
path is summarised by the consumer's `c:BB.TUI.Renderer.summarize/2`.

# `format_event`

```elixir
@spec format_event({DateTime.t(), list(), term()}) :: String.t()
```

Formats a single event as a display string.

Shows timestamp, path, and a short summary of the message payload.

## Examples

    iex> ts = ~U[2026-01-15 18:23:12.936Z]
    iex> BB.TUI.Panels.Events.format_event({ts, [:sensor, :simulated], %{payload: %{names: [:elbow], positions: [0.5]}}})
    "18:23:12 sensor.simulated   JointState 1 joint(s)"

    iex> ts = ~U[2026-01-15 18:23:12.000Z]
    iex> BB.TUI.Panels.Events.format_event(
    ...>   {ts, [:command, :move, make_ref()], %{payload: %{status: :cancelled}}}
    ...> )
    "18:23:12 command.move       move cancelled"

    iex> ts = ~U[2026-01-15 18:23:12.000Z]
    iex> BB.TUI.Panels.Events.format_event({ts, [:state_machine], %{payload: %{from: :disarmed, to: :armed}}})
    "18:23:12 state_machine      disarmed → armed"

# `format_event_details`

```elixir
@spec format_event_details({DateTime.t(), list(), term()}) :: [String.t()]
```

Formats the detail lines for an expanded event.

Returns a list of indented strings showing the message type and payload fields.

## Examples

    iex> ts = ~U[2026-01-15 18:23:12.000Z]
    iex> event = {ts, [:sensor, :simulated], %{payload: %{names: [:elbow], positions: [0.5], velocities: [0.0], efforts: [0.0]}}}
    iex> details = BB.TUI.Panels.Events.format_event_details(event)
    iex> hd(details) =~ "efforts"
    true

# `render`

```elixir
@spec render(BB.TUI.State.t(), boolean()) :: struct()
```

Renders the events panel as a List widget with formatted event entries.
Newest events appear first. Scrollable with j/k when focused.

## Examples

    iex> state = %BB.TUI.State{events: %BB.TUI.State.Events{list: [], scroll_offset: 0, paused?: false}}
    iex> widget = BB.TUI.Panels.Events.render(state, false)
    iex> widget.items
    []

# `render_panes`

```elixir
@spec render_panes(BB.TUI.State.t(), boolean(), ExRatatui.Layout.Rect.t()) :: [
  {struct(), ExRatatui.Layout.Rect.t()}
]
```

Renders the events panel as a list of `{widget, rect}` panes: the events
list plus an overlay Scrollbar pinned to the right-hand side.

The scrollbar rect is inset by one cell so it appears inside the panel's
rounded border rather than on top of it. When there are no events, only
the list pane is returned — a scrollbar with zero content would render
a track with no thumb, which is visually noisy.

## Examples

    iex> rect = %ExRatatui.Layout.Rect{x: 0, y: 0, width: 40, height: 10}
    iex> state = %BB.TUI.State{events: %BB.TUI.State.Events{list: [], scroll_offset: 0, paused?: false}}
    iex> panes = BB.TUI.Panels.Events.render_panes(state, false, rect)
    iex> length(panes)
    1

    iex> rect = %ExRatatui.Layout.Rect{x: 0, y: 0, width: 40, height: 10}
    iex> ts = ~U[2026-01-15 18:23:12.000Z]
    iex> events = [{ts, [:state_machine], %{payload: %{from: :disarmed, to: :armed}}}]
    iex> state = %BB.TUI.State{events: %BB.TUI.State.Events{list: events, scroll_offset: 0, paused?: false}}
    iex> [{_list, _list_rect}, {scrollbar, _bar_rect}] =
    ...>   BB.TUI.Panels.Events.render_panes(state, true, rect)
    iex> scrollbar.content_length
    1

# `summarize`

```elixir
@spec summarize(list(), term()) :: String.t()
```

Produces a short summary string for an event based on its path and payload.

## Examples

    iex> BB.TUI.Panels.Events.summarize([:sensor, :sim], %{payload: %{names: [:a, :b], positions: [1.0, 2.0]}})
    "JointState 2 joint(s)"

    iex> BB.TUI.Panels.Events.summarize([:state_machine], %{payload: %{from: :armed, to: :idle}})
    "armed → idle"

    iex> BB.TUI.Panels.Events.summarize([:actuator, :waist], %{payload: %{position: 1.57}})
    "waist ← position 1.570"

    iex> BB.TUI.Panels.Events.summarize([:command, :move, :ref], %{payload: %{status: :started, data: %{goal: %{angle: 1.5}}}})
    "move started angle=1.5"

    iex> BB.TUI.Panels.Events.summarize([:command, :home, :ref], %{payload: %{status: :started, data: %{goal: %{}}}})
    "home started (no args)"

    iex> BB.TUI.Panels.Events.summarize([:command, :home, :ref], %{payload: %{status: :succeeded, data: %{result: :ok}}})
    "home ✔ :ok"

    iex> BB.TUI.Panels.Events.summarize([:command, :move, :ref], %{payload: %{status: :failed, data: %{reason: :timeout}}})
    "move ✘ :timeout"

    iex> BB.TUI.Panels.Events.summarize([:command, :move, :ref], %{payload: %{status: :cancelled}})
    "move cancelled"

    iex> BB.TUI.Panels.Events.summarize([:param, :speed], %{payload: %{new_value: 42}})
    "speed = 42"

    iex> BB.TUI.Panels.Events.summarize([:unknown], %{payload: :something})
    ":something"

# `title`

```elixir
@spec title(non_neg_integer(), boolean()) :: String.t()
```

Builds the panel title as a single-string label (legacy form).

## Examples

    iex> BB.TUI.Panels.Events.title(47, false)
    " Events (47) "

    iex> BB.TUI.Panels.Events.title(47, true)
    " Events (47) ⏸ PAUSED "

    iex> BB.TUI.Panels.Events.title(0, false)
    " Events "

    iex> BB.TUI.Panels.Events.title(0, true)
    " Events ⏸ PAUSED "

# `title_line`

```elixir
@spec title_line(non_neg_integer(), boolean()) :: ExRatatui.Text.Line.t()
```

Builds the panel title as a rich-text `%Line{}` — the count renders
bold-cyan and the `⏸ PAUSED` badge renders bold-yellow when the
stream is paused.

## Examples

    iex> %ExRatatui.Text.Line{spans: spans} =
    ...>   BB.TUI.Panels.Events.title_line(47, false)
    iex> Enum.map_join(spans, "", & &1.content)
    "  4  Events (47) "

    iex> %ExRatatui.Text.Line{spans: spans} =
    ...>   BB.TUI.Panels.Events.title_line(47, true)
    iex> Enum.map_join(spans, "", & &1.content)
    "  4  Events (47)  ⏸ PAUSED "

    iex> %ExRatatui.Text.Line{spans: spans} =
    ...>   BB.TUI.Panels.Events.title_line(0, false)
    iex> Enum.map_join(spans, "", & &1.content)
    "  4  Events "

---

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