Skip to content

Commit 7b12ddf

Browse files
committed
Feat(abctl): also bind b/f for paging; label footer "[b/f] page"
Mac laptops have no dedicated Page Up/Down keys (they're fn+↑/fn+↓), so route the less/vim-style b (page up) and f (page down) through the same pager as PgUp/PgDn — uniform one-row overlap on every keyboard — and show "[b/f] page" in the events footer instead of the ⇞⇟ glyphs, which aren't obvious on a laptop. Extends the page-nav test to cover b/f. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Hai Huang <huang195@gmail.com>
1 parent 6e1fa00 commit 7b12ddf

2 files changed

Lines changed: 18 additions & 7 deletions

File tree

authbridge/cmd/abctl/tui/events_pane_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ func TestPageActivePane_EventsTable(t *testing.T) {
4141
if got := m.eventsTbl.Cursor(); got != 0 {
4242
t.Errorf("PgUp back to top: cursor=%d, want 0", got)
4343
}
44+
45+
// b/f (the keys shown in the footer, work on any keyboard) page the same way.
46+
m.pageActivePane(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("f")})
47+
if got := m.eventsTbl.Cursor(); got != h-1 {
48+
t.Errorf("f (page down) from top: cursor=%d, want %d", got, h-1)
49+
}
50+
m.pageActivePane(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("b")})
51+
if got := m.eventsTbl.Cursor(); got != 0 {
52+
t.Errorf("b (page up) back to top: cursor=%d, want 0", got)
53+
}
4454
}
4555

4656
// TestShortPhase covers the rendered string for every SessionPhase.

authbridge/cmd/abctl/tui/keys.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,13 @@ func (m *model) handleKey(msg tea.KeyMsg) tea.Cmd {
273273
m.goBottom()
274274
return nil
275275

276-
case "pgup", "pgdown", "pgdn":
276+
case "pgup", "pgdown", "pgdn", "b", "f":
277277
// Page the active pane. Sessions can hold up to session.max_events
278-
// (500) rows, so one-row-at-a-time nav isn't enough. Explicit here
279-
// (rather than relying on the table component's own binding) so the
280-
// page size carries a one-row overlap for context and the detail
281-
// viewport pages too.
278+
// (500) rows, so one-row-at-a-time nav isn't enough. b/f (less/vim
279+
// style) work on any keyboard; PgUp/PgDn map to fn+↑/fn+↓ on Mac
280+
// laptops. Explicit here (rather than the table component's own
281+
// binding) so all of them share a one-row overlap for context and the
282+
// detail viewport pages too.
282283
return m.pageActivePane(msg)
283284

284285
case "P":
@@ -403,7 +404,7 @@ func (m *model) goBottom() {
403404
// panes (namespaces/pods) never reach here; they return early in handleKey
404405
// and page via their own component's binding.
405406
func (m *model) pageActivePane(msg tea.KeyMsg) tea.Cmd {
406-
up := msg.String() == "pgup"
407+
up := msg.String() == "pgup" || msg.String() == "b"
407408
page := func(t *table.Model) {
408409
n := t.Height() - 1
409410
if n < 1 {
@@ -459,7 +460,7 @@ func (m *model) helpView() string {
459460
if m.hideInactive {
460461
skipHint = "[s] show all"
461462
}
462-
base := "[↑↓] nav [⇞⇟] page [↵] detail [esc] back [/] filter " + skipHint + " [p] pause [q] quit"
463+
base := "[↑↓] nav [b/f] page [↵] detail [esc] back [/] filter " + skipHint + " [p] pause [q] quit"
463464
// Surface the hidden-message count so a filtered timeline doesn't
464465
// look like data loss. Only annotate when hiding is on AND at
465466
// least one message was hidden.

0 commit comments

Comments
 (0)