fix: help-screen drift, dead Shift+D binding, byte/char title truncation

'?' help never mentioned the M (Profiles & Sync) menu at all, and
documented 'd'/'p'/'c' only for their generic meaning while Stats/
Activity tabs silently overload the same keys for something else —
same class of bug the earlier B4 fix addressed, just new drift since.
Also: 'D' with an explicit SHIFT-modifier guard never matched, since
terminals deliver Shift+d as an already-uppercase char with no
modifier flag (same root cause as the earlier Ctrl+A fix) — merged
into the identical plain-Delete arm instead of leaving it dead code.
Also: PR/issue title continuation used title.len() (bytes) to decide
whether to render a second line, while the actual text used
.chars() — a non-ASCII title could get a spurious blank line.
This commit is contained in:
dualfroz committed 2026-07-23 07:25:37 +02:00
1 parent 1b6a94dce6
commit 7bba2c7d0b
3 files changed
+19 -18

No files matched your search

+6 -14
View File
@@ -362,20 +362,12 @@ pub fn handle_global_key(state: &mut HubState, key: KeyEvent) -> Option<PhaseRes
state.modal = Modal::Text { title: "Edit todo — Title".into(), prompt: "Title:".into(), value: cur, tag: TextTag::TodoEditTitle(ti), max_len: Some(100) };
}
}
KeyCode::Delete | KeyCode::Char('D') if key.modifiers.contains(KeyModifiers::SHIFT) => {
if state.tab == Tab::Todos {
if let Some(ti) = state.cur_todo_idx() {
let title = state.todos[ti].title.clone();
state.modal = Modal::Confirm { message: format!("Delete '{}'?", title), tag: ConfirmTag::DeleteTodo(ti) };
}
} else if state.tab == Tab::Projects {
if let Some(pi) = state.cur_proj_idx() {
let name = state.projects[pi].name.clone();
state.modal = Modal::Confirm { message: format!("Delete project '{}'?", name), tag: ConfirmTag::DeleteProject(pi) };
}
}
}
KeyCode::Delete => {
// Terminals deliver Shift+d as an already-uppercase 'D' without a
// separate SHIFT modifier flag on the char event (same class of bug
// as the Ctrl+A fix above) — match the bare char instead of gating
// on a modifier that never actually arrives, and fold the identical
// plain-`Delete` handling into the same arm rather than duplicating it.
KeyCode::Delete | KeyCode::Char('D') => {
if state.tab == Tab::Todos {
if let Some(ti) = state.cur_todo_idx() {
let title = state.todos[ti].title.clone();
+11 -2
View File
@@ -30,7 +30,7 @@ pub static HELP_LINES: &[&str] = &[
"── Filtering & Sorting ───────────────────────",
" / Fuzzy filter repos (↑↓ move · Enter open · Esc clear)",
" S Sort & filter menu",
" d Toggle: show only dirty repos",
" d Toggle: show only dirty repos (Stats tab: preview digest instead)",
" b Toggle: show only behind repos",
" f Toggle: show only ahead repos",
" a Toggle: show archived repos (no commits >90d)",
@@ -58,7 +58,7 @@ pub static HELP_LINES: &[&str] = &[
" Space Select repo for batch action",
"",
"── Projects & Todos ──────────────────────────",
" p Project menu (create / assign / rename / delete)",
" p Project menu (create / assign / rename / delete) (Stats tab: cycle period instead)",
" + New todo",
" e Edit todo (Todos tab)",
" Space (Todos tab) Cycle todo status, or expand/collapse a group",
@@ -69,6 +69,15 @@ pub static HELP_LINES: &[&str] = &[
" D / Del Delete todo or project",
" T Scan repos for TODO/FIXME/HACK → import to Todos",
"",
"── Stats & Activity ──────────────────────────",
" p (Stats tab) Cycle period forward: 1d → 7d → 30d → 90d → 1y → all",
" < (Stats tab) Cycle period backward",
" d (Stats tab) Preview weekly digest",
" c (Activity tab) Cycle activity-kind filter",
"",
"── Settings ───────────────────────────────────",
" M (Settings tab) Profiles & Sync menu — switch/save/push/pull settings",
"",
"── Help ──────────────────────────────────────",
" ? Show this help",
" v (Overview) Open release notes in browser",
+2 -2
View File
@@ -424,7 +424,7 @@ fn render_right_pr(f: &mut Frame, state: &HubState, area: Rect) {
lines.push(r_kv("Number", &format!("#{}", pr.number), Color::DarkGray));
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(format!(" \"{}\"", pr.title.chars().take(30).collect::<String>()), Style::default().fg(Color::White).add_modifier(Modifier::BOLD))));
if pr.title.len() > 30 {
if pr.title.chars().count() > 30 {
lines.push(Line::from(Span::styled(format!(" {}", pr.title.chars().skip(30).take(30).collect::<String>()), Style::default().fg(Color::White))));
}
lines.push(Line::from(""));
@@ -459,7 +459,7 @@ fn render_right_issue(f: &mut Frame, state: &HubState, area: Rect) {
lines.push(r_kv("Number", &format!("#{}", issue.number), Color::DarkGray));
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(format!(" \"{}\"", issue.title.chars().take(30).collect::<String>()), Style::default().fg(Color::White).add_modifier(Modifier::BOLD))));
if issue.title.len() > 30 {
if issue.title.chars().count() > 30 {
lines.push(Line::from(Span::styled(format!(" {}", issue.title.chars().skip(30).take(30).collect::<String>()), Style::default().fg(Color::White))));
}
lines.push(Line::from(""));