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

Main TUI application using the `ExRatatui.App` **reducer runtime**.

Renders the dashboard layout and handles every keyboard event,
PubSub message, and side effect through a single `update/2` arrow.
Pure state transitions live in `BB.TUI.State`; this module's job is
to wire input and async results to those transitions and return
declarative `ExRatatui.Command` values for IO.

## Layout

    ┌ Safety ────────┬─ Joint Control ──────────────────────────────┐
    │ ● ARMED        │ Joint       Type  Position  Range           │
    │ Runtime: Idle  │ elbow       rev   -63.8°    ████████░░░░░░  │
    │ [a] Arm        │ gripper SIM pri    30.6mm   ███░░░░░░░░░░░  │
    │ [d] Disarm     │ ...                                         │
    ├ Commands ──────┤                                             │
    │ ▶ home   Ready │                                             │
    │   calibrate    │                                             │
    ├ Events (47) ───┴── Parameters ───────────────────────────────┤
    │ 18:23:12 sensor.sim  │ speed              100               │
    │ 18:23:11 state_m...  │ controller.kp      0.5               │
    └──────────────────────┴───────────────────────────────────────┘
     Robot | ● Armed | idle | [q]Quit [Tab]Panel [?]Help

## Reducer callbacks

  * `init/1` — validates the robot module, subscribes to PubSub
    (`{:bb, _, _}` messages flow into `update/2` as `{:info, _}`
    automatically), and snapshots ETS state. No `Task.Supervisor`
    is required: long-running command execution is owned by the
    runtime via `ExRatatui.Command.async/2`.
  * `render/2` — composes panel functions into a flat
    `[{widget, rect}]` list (the events panel contributes two panes:
    the list and an overlay `ExRatatui.Widgets.Scrollbar`).
  * `update/2` — the single dispatch arrow. Receives `{:event, ev}`
    for terminal input and `{:info, msg}` for everything else
    (PubSub, async results, subscription ticks, `send_after`
    messages). Returns `{:noreply, state}` for pure transitions or
    `{:noreply, state, commands: [cmd]}` when an effect should fire.
  * `subscriptions/1` — declares the throbber tick interval whenever
    the dashboard has something animating (a `:disarming` safety
    state or a command currently executing), and a one-shot
    `:sensor_flush` tick whenever a sensor render is pending (see
    *High-rate sensor handling*). The runtime diffs the result
    against the previous one, so timers only run when needed. This
    replaces the previously-dormant `Process.send_after`-style
    throbber tick.

## High-rate sensor handling

Robot sensor readings arrive on the `[:sensor | _]` path and can be
very fast. Two mechanisms keep the UI responsive without dropping
meaningful information:

  * `BB.TUI.State.append_event/3` debounces the event log — a repeat
    of the same `{path, payload-type}` within `throttle.debounce_ms`
    (default 1s) is dropped, so one fast sensor cannot evict every
    other event from the 100-entry log.
  * Sensor messages update state but suppress their immediate render
    (`render?: false`); a one-shot `:sensor_flush` tick
    (`throttle.flush_ms`, default ~33ms / 30fps) armed by
    `subscriptions/1` performs a single coalesced redraw. Every other
    message — key presses, command results, safety/param/state
    events — still renders immediately.

Both intervals live in the `BB.TUI.State.Throttle` substruct, so tests
can shrink or disable them (a debounce window of `0` disables it).

## Async commands

Pressing Enter on a Ready command executes it when the command has
no arguments, or enters an inline argument-edit mode when the
command declares arguments. From edit mode, Tab/Shift+Tab cycle
fields, typing edits the focused field, Enter executes with the
parsed values, and Esc exits without executing.

Execution returns a `Command.async/2` that calls
`BB.Command.await/2`, which waits on the spawned command via
`GenServer.call`, falls back to bb's `ResultCache` if the handler
finishes before we can await, and enforces the timeout internally.
The result arrives as a single `{:command_result, _}` info message
(success, error, or `{:error, :timeout}`).

## Side-effect convention

Fast, fire-and-forget calls (`Robot.arm/2`, `Robot.disarm/2`,
`Robot.set_actuator/4`, `Robot.set_parameter/4`,
`Robot.publish/4`, `Robot.force_disarm/2`) are invoked inline from
`update/2` rather than wrapped in a `Command.async/2`. They are
effectively constant-time PubSub publishes; the boilerplate of
routing through a no-op result mapper would dwarf the call. Only
`Robot.execute_command/4`, which monitors a spawned command process
and waits for its `:DOWN`, goes through `Command.async/2`.

## Configuration

The wait window for `BB.Command.await/2` is compile-time configurable
via `Application.compile_env/3`:

    # config/config.exs
    config :bb_tui, command_timeout: 30_000

Default is `30_000` ms. The test suite overrides this to `100` ms in
`config/test.exs` to keep timeout assertions snappy. Because the
value is read with `compile_env`, downstream apps need to recompile
`:bb_tui` after changing the config (`mix deps.compile bb_tui
--force`).

---

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