# ADR-0051: Bottom keybinding strip — context- and state-aware

## Status

**Accepted 2026-06-13 (issue #27).** Closes Gitea **#27**. All forks
below were escalated to the user and user-chosen before any code was
written; to be implemented test-first. Builds on ADR-0046 (nav focus),
ADR-0003 (input modes), ADR-0049 (the #29 readline keys this strip now
advertises), and ADR-0022 (the Tab-completion memo).

## Context

The bottom status line (`render_status_bar`, `ui.rs`) mixed keystrokes
with typed-command words: `Enter submit · : advanced once · mode
advanced switch · Ctrl-C quit`. That is redundant — the hint panel
already teaches `help` and `Enter` when the input is empty — and it is
static apart from a three-way mode branch, so it never reflects what the
user can actually do *right now* (navigating the sidebar, cycling a
completion, browsing history, editing a line).

Issue #27: repurpose the line as a **keybindings-only** strip that is
**context-sensitive to nav focus** and **state-aware of the current
transient interaction**, and move mode discovery into the empty-input
hint.

## Decision

### 1. The strip is keybindings-only and state-selected

A single pure function `status_bar_bindings(app) -> Vec<Binding>`
computes the strip from app state; `render_status_bar` is a thin
renderer over it (so the binding sets are unit-testable without a
Frame). `history_cursor` is private to `App`, so a small
`pub fn is_browsing_history(&self) -> bool` accessor exposes the
history-navigation predicate; `mode` / `nav_focus` / `last_completion`
are already `pub` and `effective_mode()` is a `pub` method. The state is
chosen by **priority — first match wins**:

| Priority | State (predicate) | Strip |
|---|---|---|
| 1 | **Sidebar focus** (`nav_focus` in a sidebar) | `Ctrl-O next pane · ↑↓/PgUp/PgDn scroll · Esc input` |
| 2 | **Completion memo live** (`last_completion.is_some()`) | `Tab/Shift-Tab cycle · Esc cancel · Enter run` |
| 3 | **History navigation** (`history_cursor.is_some()`) | `↑↓ browse · Esc clear · Enter run` |
| 4 | **Editing** (Input focus, input non-empty) | `Esc clear · Ctrl-A/E home/end · Ctrl-W del word · Enter run` |
| 5 | **Default** (Input focus, input empty) | `Ctrl-O sidebar · Tab complete · ↑ history · Enter run` |

Priority order matters: a completion memo or history navigation is a
non-empty-input situation, so states 2 and 3 must precede state 4. The
sidebar overlay occludes the input entirely (ADR-0046), so state 1 wins
outright.

### 2. Mode discovery moves off the strip, into the empty-input hint

The typed-command advertisements (`mode advanced` / `mode simple`
switch, the `:` one-shot) leave the strip — they are not keystrokes.
Mode discovery moves to the **empty-input hint** (`resolve_hint_lines`'s
`(None, None)` arm), in **simple mode only**:

- **Simple:** `… · \`mode advanced\` for SQL`
- **Advanced (persistent):** no pointer.

The pointer omits the verb "type" — the surrounding prompt already
implies it (we don't say "type `help`" either). Advanced mode shows
**no** pointer (user decision, post-trial): a user who switched into
advanced mode knows how they got there, and `help` covers the way back —
a "switch back" pointer only reads naturally in the moment right after
switching, so it earns its space poorly.

The one-shot advanced state's old `Backspace cancel one-shot` label is
**subsumed** by the editing state (the input is non-empty in one-shot;
Esc-clear and Backspace both cancel it). No behaviour is lost — only the
dedicated label.

### 3. Width: no drop machinery; a budget test instead

The longest strip (state 4, editing) is ≈ **65 display columns**, which
fits every supported width (90-col screencasts, 80-col terminals) with
margin — so the priority-drop / abbreviation machinery considered would
never trigger and is not built (user-confirmed). Ratatui's existing
**clip-at-edge** is the trivial fallback for pathologically narrow
(< 65-col) terminals. Instead, a **width-budget unit test** pins the
longest rendered strip within an 80-col budget, keeping the strip lean
*by construction* — a future over-long strip fails the test rather than
silently clipping in a cast.

## Forks (all user-chosen)

- **Editing state — yes:** when the input has text, surface the #29
  readline keys (Esc-clear, Ctrl-A/E, Ctrl-W); the strip stays lean
  (nav/complete/history) when empty. (vs not advertising the #29 keys.)
- **`Ctrl-C quit` — omitted** from the strip (vs always shown): quit is
  a near-universal convention; omitting it keeps the strips lean and
  matches the issue's sketch.
- **Width — budget test, no drop logic** (vs graceful priority-drop /
  abbreviation): the strips fit at supported widths, so the machinery
  would be dead weight (user's own observation).

## Consequences

- The strip now teaches the keys for the *current* situation; learners
  see `Tab/Shift-Tab cycle` exactly while cycling, the editing keys
  exactly while editing, etc.
- The #29 readline keys (ADR-0049) gain their on-screen advertisement,
  closing that ADR's deferred item.
- 15 existing full-panel insta snapshots churn (the bottom line — and,
  on empty-input views, the hint pointer — changes in every one,
  including the rebuild-confirm modal view, whose modal box is itself
  unchanged); each diff was reviewed, not blind-accepted.
- `requirements.md` is unaffected (an ADR-tracked UI refinement); the
  change is cross-referenced from the commit + this ADR.

## Tests

- **Tier-1 (`ui.rs` unit):** `status_bar_bindings` returns the expected
  key set for each of the five states (sidebar, completion-live,
  history-nav, editing, default) — the completion/history states driven
  through real key events (`update`) so the predicate transitions are
  exercised, the others by setting `App` fields; plus the width-budget
  assertion across states. (Per-state coverage is these unit tests, not
  snapshots — a one-line strip is asserted more precisely by its exact
  key list than by a full-panel snapshot.)
- **Tier-1:** the empty-input hint appends the correct mode pointer in
  Simple vs Advanced, and does **not** append it when an ambient hint is
  showing (non-empty input).
- **Tier-3 (`walking_skeleton`):** the old `status_bar_lists_quit_and_
  submit_in_all_modes` (which asserted the pre-ADR strip) is rewritten +
  renamed to assert the keystroke-only, state-aware strip end-to-end
  through the real render path (default → editing transition).
- **Tier-2 (insta):** the 15 full-panel snapshots re-accepted (each diff
  reviewed — strip line and/or hint pointer only).

## Out of scope

- **Modal-aware strip.** While a modal is open (load picker, rebuild /
  undo confirm) it owns the keyboard and carries its own in-box key
  hints; the bottom strip under a modal computes from input state
  exactly as it does today (modals render *over* the status bar). This
  issue does not redesign the modal case — pre-existing behaviour,
  unchanged and not worsened.
- A persistent/togglable help overlay listing *all* keys (the strip is a
  contextual subset, not a cheatsheet).
- Per-key colour theming beyond the existing key/label/separator styles.
- Localisation of the new label strings beyond adding catalog entries.
- The remaining I1b kill keys' (Ctrl-K/Ctrl-U) advertisement — the
  editing strip shows the highest-value subset (Esc/Ctrl-A/E/Ctrl-W) to
  stay within the width budget; Ctrl-K/U remain unadvertised muscle
  memory.
