Core Contract
The TUI should describe each user action once: its identity, keys, label,
visibility rule, enabled rule, disabled reason, and execution handler. The
dispatcher, bottom-bar hint renderer, and keybind popover then read the same
action list.
A key should not be active unless the same action is visible or intentionally
hidden. A bottom-bar hint should not point to a key that the current mode ignores,
and the keybind popover should explain all visible keys for that mode.
Key Hint Principles
- Hints describe what will happen now, not what a key usually does elsewhere.
- Do not show a key that the focused control captures for another purpose.
- Use exact verbs:
next, save, cancel, edit.
- Keep the footer for immediate actions only.
- Move general navigation and global commands to the keybind popover.
- When a modal is open, the modal owns all visible hints.
- The complete key list must come from the same action source as dispatch.
Current Direction
Recent cleanup tightened several drift points, but the footer still has too many
general navigation hints. Keys such as j/k, arrows, R,
T, ^T, ^A, L, and
q should not be repeated on every screen.
The next step is to keep the footer narrow and add a keybind help popover that
lists all keys available on the active screen, including general movement and
selection rules.
Risk
Drift gets worse with overlays. A dashboard alert action, an ESV edit form, and a
production confirmation modal may all want Enter, Esc,
or number keys. When any popover modal is active, the bottom-bar hints should
disappear and the modal should own its own complete key list.
Action Descriptor
Action {
id: ActionId,
keys: &'static [KeySpec],
label: &'static str,
scope: ActionScope,
hint: HintPlacement,
group: HintGroup,
visible: fn(&App) -> bool,
enabled: fn(&App) -> bool,
disabled_reason: fn(&App) -> Option<String>,
run: fn(&mut App, KeyEvent) -> Result<()>
}
One Source
Action table
Screen and overlay expose the actions that exist in the current mode.
Dispatcher
Key events are matched against enabled actions in precedence order.
Renderer
Footer, modal, and keybind popover render from the same filtered actions.
Hint Surfaces
| Surface |
Purpose |
What belongs there |
| Bottom bar |
Fast reminders for the few actions the user is most likely to need now. |
Primary screen actions such as save, apply, confirm, cancel, or open keybind
help. General movement keys do not belong here.
|
| Keybind popover |
Complete reference for the active screen, using the confirm-style popover chrome. |
Every visible action for the current screen, including navigation,
selection, panel switching, refresh, quit, and numbered selection.
|
| Active modal |
Complete reference for the modal currently capturing input. |
Modal-specific actions only. While a popover modal is visible, the bottom
bar is hidden behind it.
|
Keybind Popover
The keybind popover should use the same visual treatment as confirmation
modals: centered over the current screen, with the background left in place.
It is not a separate page, and it should not reset panel state.
| Group |
Contents |
| Screen actions |
Save, apply, edit, delete, refresh, open logs, resolve alert, or any other action unique to the active screen. |
| Modal actions |
If another modal is active, list only that modal's keys and hide normal screen actions. |
| Movement |
Explain arrows and j/k in the language of the current screen. |
| Selection |
Explain Enter and numbered row selection when available. |
| General commands |
List global commands that are active in this context, including quit and panel navigation. |
Mode Precedence
The active mode stack decides which actions receive key events. The topmost modal
or editor handles keys first. If it does not define an action for the key, the key
may fall through only when that behavior is explicitly allowed.
Blocking modal
Production confirmation, discard prompt, destructive action confirmation.
Usually captures Enter, Esc, arrows, and number keys.
Keybind popover
Confirm-style popover that leaves the background in place. It captures its own
close, scroll, and selection keys, and it is the only visible hint surface while open.
Inline editor
Text entry and form controls. Most printable keys belong to the editor, not the
screen below it.
Panel
ESV list, auth settings, environment picker, dashboard, logs, scripts.
Global
Quit, help, panel navigation, and commands that are safe everywhere.
Visibility and Enabled Rules
| Rule |
Use it for |
Example |
visible |
Whether the action belongs in the current UI at all. |
Show ESV restart only on the ESV panel. |
enabled |
Whether the action can run right now. |
Enable restart only when apply state is Unapplied. |
disabled_reason |
Why a visible action is unavailable. |
Restart disabled because tenant is already Restarting. |
hidden_reason |
Rare cases where an active key is intentionally omitted from hints. |
Internal debug shortcut, gated behind debug build only. |
Hint Policy
Prefer showing enabled primary actions in the bottom bar. Show disabled actions
there only when the disabled state explains important system status, such as an
ESV restart already being in progress.
Global Key Placement
General movement, panel navigation, refresh, logs, and quit are available often
enough that users will learn them, but repeating them in every footer makes the
interface noisy. They should be present in the keybind popover and omitted from
normal bottom-bar hints.
| Keys |
Meaning |
Placement |
j/k, arrows |
Move through rows, fields, or controls. |
Popover only, with context-specific wording. |
1-9 |
Select numbered rows when the current screen supports numeric selection. |
Popover only unless numeric selection is the primary interaction. |
R, T, ^T, ^A, L |
General refresh, tenant, auth, and log-oriented commands. |
Popover only on screens where the command is actually available. |
q |
Quit or leave the app, depending on the current mode. |
Popover only for normal screens; modal close keys are modal-specific. |
Context Examples
| Context |
Action |
Visibility and enabled behavior |
| ESV list |
RestartToApply on ^S |
Visible on the ESV panel. Enabled only when the cached authoritative apply
state is Unapplied and no restart request is in flight.
|
| ESV edit |
SaveField on Enter |
Footer-visible as Enter next on input fields and
Enter save on the Save button. Hidden from the footer while the
Value textarea is focused because the textarea captures Enter for newlines.
|
| ESV edit |
MoveSelector on left/right arrows |
Visible in the keybind popover whenever the selector exists. Footer-visible
only while focus is on the selector element.
|
| Environment picker |
SelectTenantN on 1-9 |
Generated from the visible tenant rows. The popover should reflect the
numeric range that actually has rows.
|
| Auth settings |
p, s, d, Enter, Esc |
These are the only actions that need bottom-bar hints on Auth Settings.
General movement and selection rules belong in the keybind popover.
|
| Add tenant |
Enter, Esc |
The footer and modal hint list should stay limited to submit and cancel.
Text editing behavior should follow the standard input control model.
|
| Auth setup |
Cancel on Esc |
Visible only when the current auth setup phase can actually be cancelled.
|
| Dashboard alert |
OpenConflict on Enter |
Enabled when the selected alert still exists and carries a conflict action.
If a background refresh resolves the alert, the action disappears.
|
Dispatch Algorithm
- Build the active action list from overlay, screen, and global scopes.
- Sort by mode precedence, with the topmost overlay first.
- If a popover modal is active, dispatch only against that modal's action list.
- Find actions whose
keys match the incoming key event.
- Run the first visible and enabled action.
- If an action is visible but disabled, optionally show its disabled reason.
- If no action matches, leave state unchanged.
Rendering Algorithm
- Ask the active mode for the same action list used by dispatch.
- Filter to visible actions.
- If any popover modal is active, hide the bottom bar and render that modal's key list.
- For normal screens, render only actions marked for footer placement.
- For the keybind popover, render every visible action grouped by purpose.
- Drop disabled actions unless their disabled state carries useful status.
- Format keys from
KeySpec, not hand-written strings.
- Group primary actions first, navigation second, global actions last.
Action IDs for Alerts
Dashboard alerts should not embed custom key handling. Each alert references
existing action IDs. When the alert is selected, the dashboard exposes those
actions in its own active action list.
ScriptConflictAlert {
id: "script-conflict:tenant:path",
actions: [
ActionId::OpenDiff,
ActionId::UseLocal,
ActionId::UseCloud,
ActionId::MarkResolved
]
}
Formatting Keys
Human-facing hints should be rendered from structured keys. For example,
Ctrl('s') becomes ^S, Enter stays
Enter, and generated numeric actions can collapse to 1-9.
Tests That Prevent Drift
| Test |
Purpose |
visible_hints_dispatch |
Every rendered enabled hint maps to an enabled action handler. |
active_keys_are_documented |
Every visible enabled action appears in the footer, active modal, or keybind popover. |
disabled_actions_do_not_run |
Disabled actions remain inert and report the expected reason. |
mode_precedence_is_stable |
Overlay keys take precedence over panel and global keys. |
generated_numeric_actions_match_rows |
Number key actions track the visible list length and never select hidden rows. |
footer_excludes_help_only_globals |
General keys such as movement, quit, tenant switching, and log shortcuts stay out of normal footers. |
popover_contains_all_visible_actions |
The keybind popover lists every visible action available in the active screen or modal. |
popover_hides_footer |
Any popover modal suppresses bottom-bar hints while it is active. |
selector_arrows_are_contextual |
Left and right arrow footer hints appear only when focus is on the ESV selector element. |
Add-a-Keybind Checklist
- Create or reuse an
ActionId.
- Define the structured
KeySpec values.
- Choose
HintPlacement: footer, popover only, modal only, or hidden with a reason.
- Add visibility and enabled predicates that match the real state machine.
- Implement the action handler in the owning screen or shared command module.
- Let the footer, modal chrome, and keybind popover render from the action descriptor.
- Keep general movement and global navigation keys out of regular bottom-bar hints.
- Add or update drift tests for the affected mode.
- For async actions, ensure the enabled predicate checks duplicate in-flight work.
- For dashboard actions, attach the action ID to alerts instead of creating bespoke keys.