From baadf82819a6e8004518b1393dc70f7f213fa5d7 Mon Sep 17 00:00:00 2001 From: Daniel Cardona Rojas Date: Thu, 13 Aug 2026 22:46:02 -0500 Subject: [PATCH 1/2] fix(tui): make Context panel help text tab-aware (#226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ContextPanel branch of get_help_bindings/get_context_bindings pushed repo-oriented Enter/Space labels unconditionally, but the panel has three tabs (Repos, Owners, Auth). Owners toggles the selected owner and Auth is read-only, so the repo labels were misleading there. Branch the Enter/Space labels on the active ContextTab: - Repos: Enter → Select repo, Space → Toggle repo - Owners: Enter/Space → Toggle owner - Auth: omit the Enter/Space bindings Resize bindings are unchanged. Adds an e2e test cycling the tabs. Co-Authored-By: Claude Opus 4.8 --- crates/codemark-tui/src/browser/bindings.rs | 38 +++++++++++++++---- crates/codemark-tui/tests/browser_e2e.rs | 42 +++++++++++++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/crates/codemark-tui/src/browser/bindings.rs b/crates/codemark-tui/src/browser/bindings.rs index da25d972..80b098d4 100644 --- a/crates/codemark-tui/src/browser/bindings.rs +++ b/crates/codemark-tui/src/browser/bindings.rs @@ -1,5 +1,5 @@ use crate::browser::right_pane::INFO_TAB_INDEX; -use crate::browser::{BrowserLayout, ContentTab, FocusArea}; +use crate::browser::{BrowserLayout, ContentTab, ContextTab, FocusArea}; use crate::ui::{HIDDEN_BINDING_PRIORITY, KeyBinding}; impl BrowserLayout { @@ -39,8 +39,19 @@ impl BrowserLayout { bindings.push(("Ctrl+S", "FTS / Sem")); } FocusArea::ContextPanel => { - bindings.push(("Enter", "Select repo")); - bindings.push(("Space", "Toggle repo")); + // Enter/Space act on whatever the active Context tab lists. The + // Auth tab is read-only, so it advertises neither. + match ContextTab::from_index(self.left_pane.context_panel.tabs.selected_index()) { + Some(ContextTab::Repos) => { + bindings.push(("Enter", "Select repo")); + bindings.push(("Space", "Toggle repo")); + } + Some(ContextTab::Owners) => { + bindings.push(("Enter", "Toggle owner")); + bindings.push(("Space", "Toggle owner")); + } + Some(ContextTab::Auth) | None => {} + } bindings.push(("+", "Increase pane")); bindings.push(("_", "Decrease pane")); } @@ -164,10 +175,23 @@ impl BrowserLayout { bindings.push(KeyBinding::new("Ctrl+S", "FTS / Sem").with_priority(90)); } FocusArea::ContextPanel => { - // Repos / Accounts - bindings.push( - KeyBinding::new("Enter", "Select").with_priority(HIDDEN_BINDING_PRIORITY), - ); + // Repos / Owners / Auth. Enter is hidden from the bar but kept for + // the help popup; the Auth tab is read-only so it advertises none. + match ContextTab::from_index(self.left_pane.context_panel.tabs.selected_index()) { + Some(ContextTab::Repos) => { + bindings.push( + KeyBinding::new("Enter", "Select") + .with_priority(HIDDEN_BINDING_PRIORITY), + ); + } + Some(ContextTab::Owners) => { + bindings.push( + KeyBinding::new("Enter", "Toggle owner") + .with_priority(HIDDEN_BINDING_PRIORITY), + ); + } + Some(ContextTab::Auth) | None => {} + } bindings.push(KeyBinding::new("+/-", "Resize").with_priority(20)); } FocusArea::FiltersPanel => { diff --git a/crates/codemark-tui/tests/browser_e2e.rs b/crates/codemark-tui/tests/browser_e2e.rs index cdfb0c2e..b9c74517 100644 --- a/crates/codemark-tui/tests/browser_e2e.rs +++ b/crates/codemark-tui/tests/browser_e2e.rs @@ -403,6 +403,48 @@ async fn search_bar_shows_esc_clear_hint() { assert!(has_esc_hint, "search-focus bindings should include 'Esc: Clear'; got: {bindings:?}"); } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn context_panel_help_labels_track_active_tab() { + let sandbox = Sandbox::new(); + let (mut layout, _sandbox) = make_layout(sandbox); + + // An empty database starts focused on the Context panel's Repos tab. + assert_eq!(layout.focus(), FocusArea::ContextPanel); + + let repos = layout.get_help_bindings(); + assert!( + repos.contains(&("Enter", "Select repo")) && repos.contains(&("Space", "Toggle repo")), + "Repos tab should describe repo actions; got: {repos:?}" + ); + + // `]` cycles the focused panel's tab: Repos -> Owners. Now Enter/Space filter + // the repo list by owner, so the labels must say so. + key_char(&mut layout, ']'); + let owners = layout.get_help_bindings(); + assert!( + owners.contains(&("Enter", "Toggle owner")) && owners.contains(&("Space", "Toggle owner")), + "Owners tab should describe owner actions; got: {owners:?}" + ); + assert!( + !owners.iter().any(|b| b.1 == "Select repo" || b.1 == "Toggle repo"), + "Owners tab must not advertise repo actions; got: {owners:?}" + ); + + // `]` again: Owners -> Auth. The Auth tab is read-only, so neither key acts. + key_char(&mut layout, ']'); + let auth = layout.get_help_bindings(); + assert!( + !auth.iter().any(|b| b.0 == "Enter" || b.0 == "Space"), + "Auth tab must not advertise Enter/Space bindings; got: {auth:?}" + ); + + // Resize bindings stay put regardless of tab. + assert!( + auth.contains(&("+", "Increase pane")) && auth.contains(&("_", "Decrease pane")), + "Resize bindings should persist across tabs; got: {auth:?}" + ); +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn search_results_reconcile_with_db_on_focus_regained() { // Two bookmarks match a "config" path search; one of them will be deleted From cfce4d1b06341d94c46c586de940df1bf6a66a20 Mon Sep 17 00:00:00 2001 From: Daniel Cardona Rojas Date: Thu, 13 Aug 2026 23:16:34 -0500 Subject: [PATCH 2/2] fix(tui): show visible Space toggle on Owners status bar; cover status bindings in test Address review feedback on #228: - Greptile P1: on the Owners tab the only status binding used HIDDEN_BINDING_PRIORITY, so the bar advertised no key to toggle an owner. Add a visible Space "Toggle owner" binding. - CodeRabbit: extend the tab-aware test to assert get_status_bindings() (the get_context_bindings path) for Repos/Owners/Auth, including that Auth carries no Enter/Space action and resize stays available. Co-Authored-By: Claude Opus 4.8 --- crates/codemark-tui/src/browser/bindings.rs | 4 +++ crates/codemark-tui/tests/browser_e2e.rs | 32 +++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/crates/codemark-tui/src/browser/bindings.rs b/crates/codemark-tui/src/browser/bindings.rs index 80b098d4..18443b9e 100644 --- a/crates/codemark-tui/src/browser/bindings.rs +++ b/crates/codemark-tui/src/browser/bindings.rs @@ -185,10 +185,14 @@ impl BrowserLayout { ); } Some(ContextTab::Owners) => { + // Enter is universal (hidden from the bar); Space is the + // discoverable toggle affordance, so advertise it as the + // primary action here. bindings.push( KeyBinding::new("Enter", "Toggle owner") .with_priority(HIDDEN_BINDING_PRIORITY), ); + bindings.push(KeyBinding::new("Space", "Toggle owner").with_priority(85)); } Some(ContextTab::Auth) | None => {} } diff --git a/crates/codemark-tui/tests/browser_e2e.rs b/crates/codemark-tui/tests/browser_e2e.rs index b9c74517..feff540e 100644 --- a/crates/codemark-tui/tests/browser_e2e.rs +++ b/crates/codemark-tui/tests/browser_e2e.rs @@ -411,11 +411,22 @@ async fn context_panel_help_labels_track_active_tab() { // An empty database starts focused on the Context panel's Repos tab. assert_eq!(layout.focus(), FocusArea::ContextPanel); + // The help popup (get_help_bindings) and the status bar (get_status_bindings, + // via the separate get_context_bindings path) must both track the active tab. let repos = layout.get_help_bindings(); assert!( repos.contains(&("Enter", "Select repo")) && repos.contains(&("Space", "Toggle repo")), "Repos tab should describe repo actions; got: {repos:?}" ); + let repos_status = layout.get_status_bindings(); + assert!( + repos_status.iter().any(|b| b.key == "Enter" && b.description == "Select"), + "Repos status bar should carry the Enter Select action; got: {repos_status:?}" + ); + assert!( + repos_status.iter().any(|b| b.key == "+/-" && b.description == "Resize"), + "Repos status bar should keep the resize binding; got: {repos_status:?}" + ); // `]` cycles the focused panel's tab: Repos -> Owners. Now Enter/Space filter // the repo list by owner, so the labels must say so. @@ -429,6 +440,18 @@ async fn context_panel_help_labels_track_active_tab() { !owners.iter().any(|b| b.1 == "Select repo" || b.1 == "Toggle repo"), "Owners tab must not advertise repo actions; got: {owners:?}" ); + let owners_status = layout.get_status_bindings(); + // Space is the discoverable toggle affordance and must be visible on the bar. + assert!( + owners_status + .iter() + .any(|b| { b.key == "Space" && b.description == "Toggle owner" && b.priority > 0 }), + "Owners status bar should show a visible Space toggle; got: {owners_status:?}" + ); + assert!( + !owners_status.iter().any(|b| b.description == "Select" || b.description == "Toggle repo"), + "Owners status bar must not advertise repo actions; got: {owners_status:?}" + ); // `]` again: Owners -> Auth. The Auth tab is read-only, so neither key acts. key_char(&mut layout, ']'); @@ -443,6 +466,15 @@ async fn context_panel_help_labels_track_active_tab() { auth.contains(&("+", "Increase pane")) && auth.contains(&("_", "Decrease pane")), "Resize bindings should persist across tabs; got: {auth:?}" ); + let auth_status = layout.get_status_bindings(); + assert!( + !auth_status.iter().any(|b| b.key == "Enter" || b.key == "Space"), + "Auth status bar must not carry an Enter/Space action; got: {auth_status:?}" + ); + assert!( + auth_status.iter().any(|b| b.key == "+/-" && b.description == "Resize"), + "Auth status bar should keep the resize binding; got: {auth_status:?}" + ); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)]