Skip to content

Menu navigation has no shared vocabulary: the same key does different things on adjacent screens #6290

Description

@Hmbown

The complaint, measured

Founder, 2026-09-16: the Fleet menu is convoluted, and we need "a more
horizontally and vertically aligned way of navigating all menus."

Both halves are one defect. menu_style.rs already single-sources the
selection vocabulary — and says so in its own contract:

The selection vocabulary is single-sourced here. Every menu, picker, and
option list renders its selected row with selected_row_style … instead of
hand-copying the trio.

This module owns styling only. Verbs, action-hint labels, keybindings,
and localized strings stay with the views.

So appearance is unified and movement is not. Every surface invents its own.

Evidence — the four Fleet views

8,501 lines across four views, each supporting a different set:

View LOC Home End PgUp/PgDn Tab ←/→
views/fleet_list.rs 1,075 yes yes no no no
views/fleet_detail.rs 1,849 no no no no no
views/fleet_roster.rs 1,010 yes no yes yes no
views/fleet_setup.rs 4,567 yes no yes yes yes

Walking Fleet list → detail → roster → setup, End works once, PageDown
works in half of them, Tab in half, and ←/→ in one. fleet_detail — the
screen you reach by pressing Enter on a worker — supports only Up/Down/Enter/Esc.

That is the convolution. No individual screen is badly built; the four disagree
about what the keys mean, so nothing transfers.

Evidence — the pickers, same story

Picker Horizontal / paging keys
model_picker.rs Left Right Tab BackTab PageUp PageDown Home End
provider_picker.rs Tab BackTab h l
session_picker.rs PageUp PageDown only
file_picker.rs PageUp PageDown only
slash_menu.rs none
mode_picker.rs none
status_picker.rs none

provider_picker and model_picker sit in the same flow — pick a provider,
then a model — and use two different idioms for the same motion: h/l
versus Left/Right. Home/End exists in exactly one picker.

The fix follows a pattern this repo already chose

A menu_nav sibling to menu_style, single-sourcing the movement
vocabulary the way menu_style single-sources the selection vocabulary:

  • one vertical axis: Up/Down, PageUp/PageDown, Home/End
  • one horizontal axis: Left/Right for moving between panes/columns, Tab/
    BackTab for moving between focus regions — one meaning each, everywhere
  • one commit key and one dismiss key
  • surfaces declare which axes they have; they do not re-implement what the
    axes do

This is deliberately not a new framework. Per AGENTS.md an abstraction must
delete caller code: the win is measured in key-handling arms removed from the
seven pickers and four Fleet views, and the module is not worth adding if it
does not delete them.

Accessibility is part of the contract, not a follow-up: every axis a surface
declares must be reachable by keyboard, and the action hints must name the keys
that actually work on that screen — today a hint can name a key the view does
not handle.

Sequencing

  1. menu_nav with the axis vocabulary, adopted by the two worst offenders
    (fleet_detail, which has no paging at all, and provider_picker, whose
    h/l contradicts its neighbour). Prove it deletes code.
  2. The remaining pickers.
  3. The Fleet views — and while there, ask whether four views and 8,501 lines is
    the right shape, or whether list/detail/roster are one surface with panes.
    That question is only answerable once movement is uniform.

Do not start step 3 before steps 1 and 2 land: rearranging Fleet on top of four
different key vocabularies would bake them in.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    • Status
      Backlog

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions