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.
Summary
Functions
Builds a rich-text %Line{} for a single event.
Formats a single event as a display string.
Formats the detail lines for an expanded event.
Renders the events panel as a List widget with formatted event entries. Newest events appear first. Scrollable with j/k when focused.
Renders the events panel as a list of {widget, rect} panes: the events
list plus an overlay Scrollbar pinned to the right-hand side.
Produces a short summary string for an event based on its path and payload.
Builds the panel title as a single-string label (legacy form).
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.
Functions
@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"
@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"
@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
@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
[]
@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
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"
@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 "
@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 "