Skip to content

Navigable Content

Navigable content is any UI surface where the cursor moves over semantic items. Lists and trees are the first adapters, but the concept also applies to future option lists, search results, pickers, and other item-oriented views.

The important distinction is still:

  • an item is the semantic object the user acts on
  • a row is a rendered terminal line

Navigation moves by item. If an item wraps to multiple rows, j still moves to the next item.

The current navigable layer is split like this:

  • ListViewModel and TreeViewModel render pure item models into lines.
  • ListNavigationState stores vim-like count and pending g state.
  • NavigableKeymap defines which key names mean move/select actions.
  • NavigableInputResolver turns keys into pure NavigationCommand values.
  • NavigableKeyHandler adapts resolved commands to Textual cursor callbacks.
  • ItemActionMessage reports semantic item actions upward.

NavigableKeyHandler is widget-layer glue. It is not domain state and it does not execute app commands.

The default NavigableKeymap is intentionally vim-like:

  • down: j
  • up: k
  • first: g g
  • last: G
  • half page down/up: ctrl+d, ctrl+u
  • select: enter, space
  • counts: 2j, 10G, and similar

Apps may provide a different keymap per widget, or use NavigableInputResolver directly when they want to handle item navigation outside the built-in widgets. Counts are part of this keymap for now; broader app-level count policies still live in the input layer.

resolve_command(...) is the preferred pure API. It returns:

  • PENDING for partial input such as a count or first-key prefix
  • MOVE with a delta for relative movement such as 5j
  • SET_CURSOR with an absolute item index for gg, G, or 3G
  • SELECT
  • NONE

resolve(...) still returns the older NavigableInput projection for compatibility.

Navigable widgets emit typed action messages:

ItemActionMessage(action=NavigableAction.SELECT, item_id="profile")

Current actions are:

  • SELECT
  • TOGGLE
  • EXPAND
  • COLLAPSE

ItemSelectedMessage still exists for compatibility and convenience, but ItemActionMessage is the more general event for app/controller integration.

ListViewWidget emits SELECT when the current item is selected.

TreeViewWidget emits:

  • SELECT for selection
  • TOGGLE for e
  • EXPAND for l
  • COLLAPSE for h

Tree expand/collapse is still widget-handled because it updates the local TreeViewModel. Apps can still listen to the action messages if those actions also need domain-side effects.