Add a searchable /config preferences panel to the TUI (#52990)

## What changed

- Group preferences into Appearance, Notifications, Input, Sessions, and Text selection tabs, with search and links to existing theme, status line, terminal title, and keyboard editors.
- Persist preference changes to the selected local configuration file, apply supported changes immediately, and show save errors, configuration overrides, and restart requirements in the panel.
- Add a delayed notification test and controls for notification timing while preserving custom event filters.
- Add `tui.copy_on_select_clear_selection`, disabled by default, to clear transcript selection highlights only after confirmed automatic clipboard delivery.
- Keep local configuration editors usable while disconnected and prevent held activation keys or buffered input from triggering actions across editor and reconnect transitions.

## Testing

Add regression tests and rendering snapshots for panel navigation, search, dependency states, editor handoffs, offline persistence, notification timers, custom filter preservation, and confirmed-copy deselection.

GitOrigin-RevId: a16d218046060fcb6e61705a803fcd9297266e6e
This commit is contained in:
Felipe Coury
2026-10-10 22:28:01 +00:00
committed by copyberry
parent de8fab6d7a
commit cc7ba33601
95 changed files with 4114 additions and 187 deletions
+15
View File
@@ -325,6 +325,7 @@ pub(crate) fn build_keymap_action_menu_params(
});
SelectionViewParams {
allow_input_when_disconnected: true,
view_id: Some(KEYMAP_ACTION_MENU_VIEW_ID),
header: Box::new(Paragraph::new(header).wrap(Wrap { trim: false })),
footer_note: Some(Line::from(vec![
@@ -392,6 +393,7 @@ pub(crate) fn build_keymap_replace_binding_menu_params(
.collect();
SelectionViewParams {
allow_input_when_disconnected: true,
view_id: Some(KEYMAP_REPLACE_BINDING_MENU_VIEW_ID),
header: Box::new(Paragraph::new(header).wrap(Wrap { trim: false })),
footer_hint: Some(picker_hint_line_for_keymap(&runtime_keymap.list)),
@@ -416,6 +418,7 @@ pub(crate) fn build_keymap_conflict_params(
KeymapCaptureMode::SingleKey
};
SelectionViewParams {
allow_input_when_disconnected: true,
header: Box::new(
Paragraph::new(vec![
Line::from("Shortcut Conflict".bold()),
@@ -675,6 +678,10 @@ fn key_parts_to_config_key_spec(
let key = match code {
KeyCode::Enter => "enter".to_string(),
KeyCode::Tab => "tab".to_string(),
KeyCode::BackTab => {
modifiers.insert(KeyModifiers::SHIFT);
"tab".to_string()
}
KeyCode::Backspace => "backspace".to_string(),
KeyCode::Esc => "esc".to_string(),
KeyCode::Delete => "delete".to_string(),
@@ -1766,6 +1773,14 @@ mod tests {
key_event_to_config_key_spec(KeyEvent::new(KeyCode::PageDown, KeyModifiers::SHIFT)),
Ok("shift-page-down".to_string())
);
assert_eq!(
key_event_to_config_key_spec(KeyEvent::from(KeyCode::BackTab)),
Ok("shift-tab".to_string())
);
assert_eq!(
key_event_to_config_key_spec(KeyEvent::new(KeyCode::Tab, KeyModifiers::SHIFT)),
Ok("shift-tab".to_string())
);
}
#[test]