Add transcript search and per-activity detail controls (#46734)

## What changed

- Add incremental, case-insensitive literal search across transcript details, loading older history as needed. Use `F3` to start Find, or `/` in the transcript pager, and navigate matches in either direction. Closing Find restores the previous presentation and reading position.
- Add clickable activity detail controls and `F4` keyboard focus for inspecting individual activity groups in the owned transcript. Preserve expansion state as live activity becomes history.
- Expose configurable `global.find_transcript`, `global.focus_activity`, and `pager.find` bindings under `tui.keymap`. Let new defaults yield to existing custom bindings and show available completions for pending chords.

## Testing

Add regression coverage for Unicode matching, bounded scanning, paginated and changing history, selection during search, composer input isolation, activity controls, and custom shortcut conflicts.

GitOrigin-RevId: 5bcbcdfbca7c5c296c635ec9b079ccd7a908e336
This commit is contained in:
Eric Traut
2026-09-20 00:06:25 +00:00
committed by copyberry
parent f1feda2299
commit df7f717c85
77 changed files with 4774 additions and 481 deletions
+48
View File
@@ -96,6 +96,10 @@ pub struct TuiGlobalKeymap {
pub open_agents: Option<KeybindingsSpec>,
/// Open the transcript overlay.
pub open_transcript: Option<KeybindingsSpec>,
/// Find text in the full transcript.
pub find_transcript: Option<KeybindingsSpec>,
/// Focus activity groups in the owned transcript to inspect their details.
pub focus_activity: Option<KeybindingsSpec>,
/// Open the external editor for the current draft.
pub open_external_editor: Option<KeybindingsSpec>,
/// Copy the last agent response to the clipboard.
@@ -401,6 +405,8 @@ pub struct TuiPagerKeymap {
pub close: Option<KeybindingsSpec>,
/// Close the transcript overlay via its dedicated toggle key.
pub close_transcript: Option<KeybindingsSpec>,
/// Find text in a transcript pager.
pub find: Option<KeybindingsSpec>,
}
/// List selection context keybindings for popup-style selectable lists.
@@ -769,6 +775,48 @@ mod tests {
assert!(keymap.global.open_transcript.is_some());
}
#[test]
fn transcript_find_accepts_custom_chords_and_can_be_disabled() {
for (bindings, expected) in [
(
"'ctrl-x f'",
KeybindingsSpec::One(KeybindingSpec("ctrl-x f".to_owned())),
),
("[]", KeybindingsSpec::Many(Vec::new())),
] {
for (context, action) in [("pager", "find"), ("global", "find_transcript")] {
let keymap: TuiKeymap =
toml::from_str(&format!("[{context}]\n{action} = {bindings}\n"))
.expect("Find binding");
let mut expected_keymap = TuiKeymap::default();
if context == "pager" {
expected_keymap.pager.find = Some(expected.clone());
} else {
expected_keymap.global.find_transcript = Some(expected.clone());
}
assert_eq!(keymap, expected_keymap);
}
}
}
#[test]
fn activity_focus_accepts_custom_chords_and_can_be_disabled() {
for (bindings, expected) in [
(
"'ctrl-x t'",
KeybindingsSpec::One(KeybindingSpec("ctrl-x t".to_owned())),
),
("[]", KeybindingsSpec::Many(Vec::new())),
] {
let keymap: TuiKeymap =
toml::from_str(&format!("[global]\nfocus_activity = {bindings}\n"))
.expect("activity focus binding");
let mut expected_keymap = TuiKeymap::default();
expected_keymap.global.focus_activity = Some(expected);
assert_eq!(keymap, expected_keymap);
}
}
#[test]
fn minus_bindings_under_global_context_are_accepted() {
for (spec, expected) in [