Repository navigation
Description
Problem
The short menu screens — ActionSelect, ResumeSelect, AgentSelect, PermissionSelect, plus the new-session mode picker — accept Up/Down for navigation but stop at the first/last item. To get from the bottom item back to the top you have to hit Up 5–6 times.
Worse, the same screens already wrap on Tab / Shift-Tab but not on the arrow keys, so the behavior is inconsistent within a single screen.
Code: the inconsistency, in one place
src/tui/mod.rs ui_action_select (L1095–1111):
// Tab / Shift-Tab: wrap with modulo
app.action_index = (app.action_index + action_count - 1) % action_count; // Shift-Tab
...
app.action_index = (app.action_index + 1) % action_count; // Tab
// Up / Down: bounded, no wrap
} else if (ui.key_code(slt::KeyCode::Up) || ...) && app.action_index > 0 {
app.action_index -= 1;
} else if (ui.key_code(slt::KeyCode::Down) || ...) && app.action_index < action_count - 1 {
app.action_index += 1;
}Tab wraps, Up/Down doesn't. Same screen, same conceptual action ("next/prev menu item"), two different behaviors. Same shape repeats in ui_resume_select, ui_agent_select, and the new-session picker.
Why wrap is the right default for these screens
These are all short, fixed menus (typically 3–6 items: Resume / Open / Cd / Pin / Delete / Back, or the resume-mode list, or the installed-agent list). On a 6-item menu, hitting Up at item 1 to jump to item 6 is the standard expectation — fzf, vim :menu, every TUI launcher I can think of behaves this way. The Tab path already commits to that model; the arrow path just hasn't caught up.
The browse list (main session list) is a different story — it can be hundreds of rows and wrap-around there would feel disorienting (you scroll past the bottom and end up at the top with no clear "new page" cue). Keep wrap off in Browse / GroupedBrowse / BulkDelete. Add wrap only on the short-menu screens.
Fix sketch
Replace the bounded Up/Down branches in each menu screen with the same modulo math the Tab path already uses:
if (ui.key_code(slt::KeyCode::Up) || ui.key_mod('p', CONTROL) || ui.key_mod('k', CONTROL))
&& action_count > 0
{
app.action_index = (app.action_index + action_count - 1) % action_count;
}
if (ui.key_code(slt::KeyCode::Down) || ui.key_mod('n', CONTROL) || ui.key_mod('j', CONTROL))
&& action_count > 0
{
app.action_index = (app.action_index + 1) % action_count;
}Apply identically to ui_resume_select (resume_mode_index / resume_mode_options.len()), ui_agent_select (agent_index / installed_agents().len()), ui_permission_select, and the new-session mode picker (mode_index / mode_options.len()).
Out of scope
Browse/GroupedBrowse/BulkDelete: long lists, keep current bounded behavior. (If a user wantsj/kwrap on Browse later, that's a separate UX call.)
Severity
Low. UX papercut. But it's hit on every action menu — Resume / Delete / Open are routed through ActionSelect — so it shows up several times per session.
Activity
release: v0.11.1 — SLT 0.20 bump + UX polish
subinium commentedon May 4, 2026
Fixed in #42 (v0.11.1). ActionSelect / AgentSelect / PermissionSelect / ResumeSelect arrow keys now cycle-wrap (first → last on Up, last → first on Down), matching the existing Tab/BackTab wrap behavior.