Skip to content

Extract app keyboard shortcuts, fix reverse paging, and deflake CI#144

Merged
benvinegar merged 6 commits intomainfrom
refactor/app-keyboard-shortcuts
Mar 31, 2026
Merged

Extract app keyboard shortcuts, fix reverse paging, and deflake CI#144
benvinegar merged 6 commits intomainfrom
refactor/app-keyboard-shortcuts

Conversation

@benvinegar
Copy link
Copy Markdown
Member

@benvinegar benvinegar commented Mar 30, 2026

Why

  • the shortcut logic in src/ui/App.tsx had grown into a large useKeyboard(...) block that was hard to change safely
  • this keeps scope precedence explicit in one dedicated hook while rolling in the real fixes and test stabilization that fell out of the refactor

Summary

  • extract scoped keyboard handling into src/ui/hooks/useAppKeyboardShortcuts.ts
  • add shared key alias matchers in src/ui/lib/keyboard.ts
  • keep precedence explicit for pager, help, open menu, filter focus, and normal app shortcuts
  • restore less-style Shift+Space reverse paging by making it reachable again
  • keep pinned file headers in sync when hunk navigation jumps across files after the main rebase
  • deflake shortcut interaction coverage and the MCP e2e port-conflict test

Testing

  • bun run typecheck
  • bun test
  • bun run test:integration
  • bun run test:tty-smoke

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 30, 2026

Greptile Summary

This PR extracts the large inline useKeyboard(...) block from App.tsx into a dedicated useAppKeyboardShortcuts hook, and moves the repeated key-alias predicates into a shared src/ui/lib/keyboard.ts utility module backed by new unit tests. The refactoring is clean and behaviorally equivalent to the original — mode precedence (pager → help → menu → filter → default) is faithfully preserved, the new callbacks are well-scoped, and the public utility functions are well-documented.

Key observations:

  • isShiftSpacePageUpKey is unreachable in both handlePagerShortcut and handleAppShortcut: isPageDownKey includes the bare isSpaceKey check (no shift guard) and is always evaluated first, so Shift+Space is always consumed as page-down. This was present in the original code but is more visible now that both helpers are named exports with JSDoc.
  • Test coverage for isPageDownKey omits the name: "f" alias (only sequence: "f" is exercised).

Confidence Score: 5/5

Safe to merge — the refactoring is behaviourally equivalent to the original and all remaining findings are P2 style/cleanup items.

No new logic errors are introduced. The only flagged issue (Shift+Space being shadowed by isPageDownKey) is a pre-existing condition faithfully preserved by the refactor. All other observations are test coverage gaps or naming clarity suggestions, none of which affect runtime behaviour.

No files require special attention; the unreachable isShiftSpacePageUpKey path in useAppKeyboardShortcuts.ts is worth a follow-up but does not block merge.

Important Files Changed

Filename Overview
src/ui/hooks/useAppKeyboardShortcuts.ts New hook containing all extracted keyboard handling. Logic is structurally sound and scope ordering is correct, but isShiftSpacePageUpKey is shadowed by the earlier isPageDownKey check in both handlePagerShortcut and handleAppShortcut.
src/ui/lib/keyboard.ts Clean utility module for key-alias predicates; all functions are well-documented. isSpaceKey is intentionally unexported (internal helper).
src/ui/App.tsx Keyboard handling cleanly removed and delegated to useAppKeyboardShortcuts; new memoized callbacks correctly extracted and added to buildAppMenus dep arrays.
test/ui-lib.test.ts New createKeyEvent factory and keyboard-alias tests cover positive and negative cases for all exported helpers; name: "f" alias for isPageDownKey is not explicitly exercised.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    K([KeyEvent]) --> MT{f10 pressed?}
    MT -- yes, consumed --> DONE([return])
    MT -- no --> PM{pagerMode?}
    PM -- yes --> PS["handlePagerShortcut\nq/esc → quit\nspace/f/pgdn → scrollDown\nb/pgup → scrollUp\nd → halfDown\nu → halfUp\nj/down → stepDown\nk/up → stepUp\nhome/end → scrollContent\nw → lineWrap"]
    PS --> DONE
    PM -- no --> HS{"showHelp and esc?"}
    HS -- yes --> CH[closeHelp] --> DONE
    HS -- no --> MS{"activeMenuId set?"}
    MS -- handled --> DONE
    MS -- not handled --> FS{"focusArea = filter?"}
    FS -- handled --> DONE
    FS -- no --> AS["handleAppShortcut\nq/esc → quit\n? → toggleHelp\ntab → toggleFocus\n/ → focusFilter\nscroll keys\n0/1/2 → layout\ns/r/t/a/l/w/m → toggles\nhunk navigation"]
    AS --> DONE
Loading

Reviews (1): Last reviewed commit: "refactor: extract app keyboard shortcuts..." | Re-trigger Greptile

Comment on lines +111 to +118
if (isPageDownKey(key)) {
scrollDiff(1, "viewport");
return;
}

if (isPageUpKey(key) || isShiftSpacePageUpKey(key)) {
scrollDiff(-1, "viewport");
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 isShiftSpacePageUpKey is unreachable

isPageDownKey includes the unguarded isSpaceKey helper, which matches any Space key event regardless of the shift modifier. Because isPageDownKey is checked first (line 111), a Shift+Space key press is always consumed as a page-down, and the isShiftSpacePageUpKey branch (line 116) is never reached. The same ordering issue applies to handleAppShortcut at lines 254–261.

This was present in the original code, but naming isShiftSpacePageUpKey as a documented, exported function with a JSDoc comment makes the dead-code more visible.

Two reasonable resolutions:

  • If the less-style Shift+Space reverse-page behaviour is intended to work, isPageDownKey should guard its space check with !key.shift so that shifted Space falls through to the page-up branch.
  • If the feature is not intended, the isShiftSpacePageUpKey export and its call sites can be removed to avoid the misleading impression that it is active.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I updated isPageDownKey to ignore shifted Space so Shift+Space now reaches the reverse-page path again, and added regression coverage for both the shared key matcher and the app interaction path.

Responded by pi-coding-agent using unknown-model.

Comment on lines +177 to +192
test("keyboard alias helpers normalize the shared scroll shortcut keys", () => {
expect(isPageDownKey(createKeyEvent({ name: "pagedown" }))).toBe(true);
expect(isPageDownKey(createKeyEvent({ name: "space" }))).toBe(true);
expect(isPageDownKey(createKeyEvent({ sequence: "f" }))).toBe(true);
expect(isPageUpKey(createKeyEvent({ name: "pageup" }))).toBe(true);
expect(isPageUpKey(createKeyEvent({ sequence: "b" }))).toBe(true);
expect(isShiftSpacePageUpKey(createKeyEvent({ name: "space", shift: true }))).toBe(true);
expect(isHalfPageDownKey(createKeyEvent({ name: "d" }))).toBe(true);
expect(isHalfPageUpKey(createKeyEvent({ sequence: "u" }))).toBe(true);
expect(isStepDownKey(createKeyEvent({ name: "down" }))).toBe(true);
expect(isStepDownKey(createKeyEvent({ sequence: "j" }))).toBe(true);
expect(isStepUpKey(createKeyEvent({ name: "up" }))).toBe(true);
expect(isStepUpKey(createKeyEvent({ sequence: "k" }))).toBe(true);
expect(isPageDownKey(createKeyEvent({ name: "q" }))).toBe(false);
expect(isShiftSpacePageUpKey(createKeyEvent({ name: "space", shift: false }))).toBe(false);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing alias coverage in isPageDownKey / isPageUpKey tests

The test exercises { sequence: "f" } for isPageDownKey but never { name: "f" }, and { sequence: "b" } for isPageUpKey but never { name: "b" }. Since the implementations treat key.name and key.sequence as separate branches, the name-based paths go untested. Consider adding:

expect(isPageDownKey(createKeyEvent({ name: "f" }))).toBe(true);
expect(isPageUpKey(createKeyEvent({ name: "b" }))).toBe(true);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the missing key.name === "f" and key.name === "b" assertions while expanding the keyboard alias coverage.

Responded by pi-coding-agent using unknown-model.

@benvinegar benvinegar force-pushed the refactor/app-keyboard-shortcuts branch 3 times, most recently from dcf66b1 to 0a2607f Compare March 31, 2026 14:04
@benvinegar benvinegar changed the title Refactor App keyboard handling into a dedicated hook Extract app keyboard shortcuts, fix reverse paging, and deflake CI Mar 31, 2026
@benvinegar benvinegar force-pushed the refactor/app-keyboard-shortcuts branch from 0a2607f to cefd4a7 Compare March 31, 2026 14:45
@benvinegar benvinegar merged commit e526519 into main Mar 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant