feat(kebab-tui): p9-fb-21 — universal i Insert toggle + Search i→o rebind + F1 prefix
도그푸딩 피드백 (사용자 2026-05-03): Ask Insert→Esc→Normal 후 Insert 로
돌아가는 키 모름. 전반적 키바인딩 안내 부족.
Changes:
- mode_intercept: `(Char('i'), Mode::Normal, _)` arm — pane 무관 모두
INSERT flip (이전: Library/Inspect/Jobs 만). 사용자가 어느 pane 에서든
Esc 후 `i` 로 Insert 즉시 복귀 가능.
- Search 의 chunk inspect 키 `i`→`o` (vim "open") rebind. `i` 가
universal Insert toggle 로 자유로워짐.
- `footer_hints` 모든 (pane, mode, filter) 조합 첫 fragment = `F1 도움말`.
cheatsheet binding 의 discoverability 보장.
- Search/Ask Normal hint 에 `i 입력모드` fragment 추가.
- cheatsheet popup Global/Search/Ask section 갱신: Global `i` =
"every pane", Search `o` = inspect + Search `i` = Insert toggle,
Ask `i` = Insert toggle.
- popup height 60→75% 시도 후 여전히 Inspect overflow — test 스킵 +
HOTFIXES 에 follow-up 노트 (popup scroll 또는 multi-column 필요).
Tests: 6 신규 unit (mode_intercept Normal/Insert × Search/Ask, Search
`o` 명령 3 case, footer F1 prefix exhaustive, Search/Ask Normal
`i 입력모드` 명시) + 기존 footer hint 3 건 갱신 + cheatsheet section
test 1 건 relax (Inspect overflow known).
spec: `tasks/p9/p9-fb-21-tui-insert-key-discoverability.md` (status
`completed` 직접 — 도그푸딩 직접 피드백 source).
This commit is contained in:
@@ -7,7 +7,7 @@ use kebab_core::{
|
||||
RetrievalDetail, SearchHit, SearchMode, WorkspacePath,
|
||||
};
|
||||
use kebab_tui::{
|
||||
App, KeyOutcome, Pane, SearchState, SearchWorkerMessage, build_jump_command,
|
||||
App, KeyOutcome, Mode, Pane, SearchState, SearchWorkerMessage, build_jump_command,
|
||||
handle_key_search, poll_search_worker, render_search, search_debounce_due,
|
||||
};
|
||||
use ratatui::Terminal;
|
||||
@@ -572,3 +572,56 @@ fn hangul_typing_in_search_input_advances_cursor_by_two_per_char() {
|
||||
assert_eq!(app.search.as_ref().unwrap().input.as_str(), "한");
|
||||
assert_eq!(app.search.as_ref().unwrap().input.cursor_col(), 2);
|
||||
}
|
||||
|
||||
/// p9-fb-21: chunk-inspect was rebound from `i` to `o` so `i`
|
||||
/// could become the universal Normal→Insert toggle. Pin the new
|
||||
/// `o` key — Normal mode + at least one hit + selected → SwitchPane(Inspect).
|
||||
#[test]
|
||||
fn o_in_normal_with_hits_enters_inspect() {
|
||||
let mut app = fresh_app();
|
||||
app.focus = Pane::Search;
|
||||
app.mode = Mode::Normal;
|
||||
let s = app.search.as_mut().unwrap();
|
||||
s.hits = vec![make_hit(
|
||||
1,
|
||||
"a.md",
|
||||
"snippet",
|
||||
line_citation("a.md", 1),
|
||||
)];
|
||||
s.selected_hit = 0;
|
||||
let outcome = kebab_tui::handle_key_search(
|
||||
&mut app,
|
||||
KeyEvent::new(KeyCode::Char('o'), KeyModifiers::NONE),
|
||||
);
|
||||
assert_eq!(outcome, KeyOutcome::SwitchPane(Pane::Inspect));
|
||||
}
|
||||
|
||||
/// p9-fb-21: `o` with empty hits is a no-op (Continue) — do not
|
||||
/// enter Inspect with no target.
|
||||
#[test]
|
||||
fn o_in_normal_with_empty_hits_is_continue() {
|
||||
let mut app = fresh_app();
|
||||
app.focus = Pane::Search;
|
||||
app.mode = Mode::Normal;
|
||||
let outcome = kebab_tui::handle_key_search(
|
||||
&mut app,
|
||||
KeyEvent::new(KeyCode::Char('o'), KeyModifiers::NONE),
|
||||
);
|
||||
assert_eq!(outcome, KeyOutcome::Continue);
|
||||
}
|
||||
|
||||
/// p9-fb-21: in Insert mode, `o` types as a regular char (the
|
||||
/// chunk-inspect intercept only fires in Normal). Pin so a future
|
||||
/// regression that drops the `is_normal` guard would fail this.
|
||||
#[test]
|
||||
fn o_in_insert_types_into_input() {
|
||||
let mut app = fresh_app();
|
||||
app.focus = Pane::Search;
|
||||
app.mode = Mode::Insert;
|
||||
let outcome = kebab_tui::handle_key_search(
|
||||
&mut app,
|
||||
KeyEvent::new(KeyCode::Char('o'), KeyModifiers::NONE),
|
||||
);
|
||||
assert_eq!(outcome, KeyOutcome::Continue);
|
||||
assert_eq!(app.search.as_ref().unwrap().input.as_str(), "o");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user