The dashboard is fully keyboard-driven. Global keys work from any panel; the rest are scoped to the focused panel. The help overlay (?) shows the same reference in-app.

Global

KeyAction
qQuit
[ ]Switch top-level tab (Control Panel / Visualization)
TabCycle to the next panel (Control Panel tab)
Shift+TabCycle to the previous panel (Control Panel tab)
1 2 3 4 5Jump directly to the panel whose title shows [N]
?Toggle help overlay
aArm robot
dDisarm robot
fForce disarm (error state only)

Each panel's title carries a bold-cyan [N] badge that mirrors the number key for that panel: [1] Safety, [2] Commands, [3] Joints, [4] Events, [5] Parameters.

Visualization tab

The Visualization tab renders the live robot in 3D from its URDF topology and joint positions. The camera orbits the robot and reposes in real time as sensor data arrives.

KeyAction
Left / Right or h / lOrbit the camera
Up / Down or k / jTilt the camera
+ / -Zoom in / out
rReset the camera
mCycle render mode (auto / kitty / sixel / iterm2 / half-block / braille / ascii)

Events panel

KeyAction
j / DownScroll down
k / UpScroll up
EnterShow event details
pPause / resume
cClear events

Commands panel

KeyAction
j / DownSelect next
k / UpSelect previous
EnterExecute (or enter argument edit mode when the command declares arguments)

Command edit mode

Active when the selected command declares arguments and Enter is pressed.

KeyAction
Tab / DownFocus next argument
Shift+Tab / UpFocus previous argument
Printable keyAppend to focused argument
BackspaceDelete last char of focused arg
/ hCycle to previous value (enum args only)
/ lCycle to next value (enum args only)
EnterExecute with current values
EscExit edit mode (keeps values)

Enum-typed args ({:in, [...]} in the Spark schema) render as ‹ value › chevrons and respond to / (or h/l) instead of needing the atom typed literally. For non-enum args, h/l continue to append to the buffer; / are no-ops outside of enum picks. Values are parsed before dispatch: "true"/"false" become booleans, ":foo" an atom, numeric an integer or float, otherwise a string.

Joints panel

KeyAction
j / DownSelect next joint
k / UpSelect previous joint
l / RightIncrease position (1% step)
h / LeftDecrease position (1% step)
LIncrease position (10% step)
HDecrease position (10% step)

Parameters panel

KeyAction
j / DownSelect next parameter
k / UpSelect previous parameter
l / RightIncrease value by one step (cycles a fixed-set parameter forward)
h / LeftDecrease value by one step (cycles a fixed-set parameter backward)
LIncrease value by ten steps
HDecrease value by ten steps
EnterToggle a boolean parameter, or open the inline editor on a string / atom parameter
tCycle to the next bridge tab (Local → bridges → Local)

Step size is 1% of the declared range when min / max are known — the min / max metadata BB.Parameter.list/2 reports on the Local tab (bb 0.30+), or the bridge's flat :min / :max keys on a bridge tab — and the new value is clamped to the bounds. Parameters without bounds use an absolute step of +1 for integers and +0.1 for floats. Unit-typed parameters ({:unit, :meter} and friends) render as magnitude plus canonical unit name (0.500 meter) and step in that unit; a bound declared in a different unit is converted before sizing and clamping. The same keys dispatch through BB.Parameter.set on the Local tab and BB.Parameter.set_remote on a bridge tab; a successful remote set refetches that bridge's parameter list so the cached values stay in sync.

A parameter declared with an {:in, values} type doesn't free-text edit — h/l cycle it through its allowed values with wrap-around, whatever their type. While the panel is focused, the selected parameter's doc renders as a dim title on the panel's bottom border.

Parameter edit mode

Active when Enter is pressed on a string or plain-atom parameter on the Local tab. The value cell turns into a text buffer (prefilled with the current value; an atom is prefilled with its leading :), and the status bar swaps to commit / cancel hints.

KeyAction
Printable keyAppend to the buffer
BackspaceDelete last char of the buffer
EnterCommit — a leading : reads as an atom, anything else as a string
EscCancel, keeping the current value

The editor is modal: its keys are matched ahead of the global ones, so q, a, t, and the number keys land in the buffer instead of quitting, arming, or switching tabs. bb validates the committed value against the parameter's schema, so a wrong type surfaces as a refused set in the event log rather than a crash. Bridge tabs don't open the editor — numeric stepping and boolean toggling remain the remote surface.