# 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` 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.