BB.TUI.Panels.Events (BB.TUI v0.1.0)

Copy Markdown View Source

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

event_line(arg)

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

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

segmentcolor
timestampcyan
pathblue (Theme.path_style/0)
summarywhite

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"

format_event(arg)

@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(arg)

@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(state, focused?)

@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(state, focused?, rect)

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(arg1, message)

@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(count, bool)

@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(count, bool)

@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 "