Skip to content

Commit 2de496e

Browse files
committed
feat(cli): add PageUp/PageDown keyboard shortcuts for scrolling chat history
- Add use-scroll-management.ts hook for scroll state management - Update use-chat-keyboard.ts with PageUp/PageDown handlers - Extend keyboard-actions.ts with scroll actions - Wire up scroll management in chat.tsx
1 parent 63f4f08 commit 2de496e

File tree

4 files changed

+60
-3
lines changed

4 files changed

+60
-3
lines changed

cli/src/chat.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ export const Chat = ({
394394
return isUserCollapsingRef.current
395395
}, [])
396396

397-
const { scrollToLatest, scrollboxProps, isAtBottom } = useChatScrollbox(
397+
const { scrollToLatest, scrollUp, scrollDown, scrollboxProps, isAtBottom } = useChatScrollbox(
398398
scrollRef,
399399
messages,
400400
isUserCollapsing,
@@ -1275,6 +1275,8 @@ export const Chat = ({
12751275
}
12761276
})
12771277
},
1278+
onScrollUp: scrollUp,
1279+
onScrollDown: scrollDown,
12781280
onOpenBuyCredits: () => {
12791281
// If credits have been restored, just return to default mode
12801282
if (areCreditsRestored()) {
@@ -1314,6 +1316,8 @@ export const Chat = ({
13141316
inputRef,
13151317
handleCtrlC,
13161318
clearQueue,
1319+
scrollUp,
1320+
scrollDown,
13171321
],
13181322
)
13191323

cli/src/hooks/use-chat-keyboard.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ export type ChatKeyboardHandlers = {
7575
onPasteImagePath: (imagePath: string) => void
7676
onPasteText: (text: string) => void
7777

78+
// Scroll handlers
79+
onScrollUp: () => void
80+
onScrollDown: () => void
81+
7882
// Out of credits handler
7983
onOpenBuyCredits: () => void
8084
}
@@ -229,6 +233,12 @@ function dispatchAction(
229233
}
230234
return true
231235
}
236+
case 'scroll-up':
237+
handlers.onScrollUp()
238+
return true
239+
case 'scroll-down':
240+
handlers.onScrollDown()
241+
return true
232242
case 'open-buy-credits':
233243
handlers.onOpenBuyCredits()
234244
return true

cli/src/hooks/use-scroll-management.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ const SCROLL_NEAR_BOTTOM_THRESHOLD = 1
99
const ANIMATION_FRAME_INTERVAL_MS = 16 // ~60fps
1010
const DEFAULT_SCROLL_ANIMATION_DURATION_MS = 200
1111

12+
// Page scroll amount (fraction of viewport height)
13+
const PAGE_SCROLL_FRACTION = 0.8
14+
1215
// Delay before auto-scrolling after content changes
1316
const AUTO_SCROLL_DELAY_MS = 50
1417

@@ -86,6 +89,30 @@ export const useChatScrollbox = (
8689
animateScrollTo(maxScroll)
8790
}, [scrollRef, animateScrollTo])
8891

92+
const scrollUp = useCallback((): void => {
93+
const scrollbox = scrollRef.current
94+
if (!scrollbox) return
95+
96+
const viewportHeight = scrollbox.viewport.height
97+
const scrollAmount = Math.floor(viewportHeight * PAGE_SCROLL_FRACTION)
98+
const targetScroll = Math.max(0, scrollbox.scrollTop - scrollAmount)
99+
animateScrollTo(targetScroll)
100+
}, [scrollRef, animateScrollTo])
101+
102+
const scrollDown = useCallback((): void => {
103+
const scrollbox = scrollRef.current
104+
if (!scrollbox) return
105+
106+
const viewportHeight = scrollbox.viewport.height
107+
const maxScroll = Math.max(
108+
0,
109+
scrollbox.scrollHeight - viewportHeight,
110+
)
111+
const scrollAmount = Math.floor(viewportHeight * PAGE_SCROLL_FRACTION)
112+
const targetScroll = Math.min(maxScroll, scrollbox.scrollTop + scrollAmount)
113+
animateScrollTo(targetScroll)
114+
}, [scrollRef, animateScrollTo])
115+
89116
useEffect(() => {
90117
const scrollbox = scrollRef.current
91118
if (!scrollbox) return
@@ -149,6 +176,8 @@ export const useChatScrollbox = (
149176

150177
return {
151178
scrollToLatest,
179+
scrollUp,
180+
scrollDown,
152181
scrollboxProps: {},
153182
isAtBottom,
154183
}

cli/src/utils/keyboard-actions.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ export type ChatKeyboardAction =
9393
| { type: 'bash-history-up' }
9494
| { type: 'bash-history-down' }
9595

96+
// Scroll actions
97+
| { type: 'scroll-up' }
98+
| { type: 'scroll-down' }
99+
96100
// Paste action (dispatcher checks clipboard content to route to image or text handler)
97101
| { type: 'paste' }
98102

@@ -126,6 +130,8 @@ export function resolveChatKeyboardAction(
126130
(key.name === 'return' || key.name === 'enter') &&
127131
!key.shift &&
128132
!hasModifier(key)
133+
const isPageUp = key.name === 'pageup' && !hasModifier(key)
134+
const isPageDown = key.name === 'pagedown' && !hasModifier(key)
129135

130136
// Priority 0: Out of credits mode - Enter opens buy credits page
131137
if (state.inputMode === 'outOfCredits') {
@@ -312,12 +318,20 @@ export function resolveChatKeyboardAction(
312318
return { type: 'unfocus-agent' }
313319
}
314320

315-
// Priority 13: Paste (ctrl-v)
321+
// Priority 13: Scroll with PageUp/PageDown
322+
if (isPageUp) {
323+
return { type: 'scroll-up' }
324+
}
325+
if (isPageDown) {
326+
return { type: 'scroll-down' }
327+
}
328+
329+
// Priority 14: Paste (ctrl-v)
316330
if (isCtrlV) {
317331
return { type: 'paste' }
318332
}
319333

320-
// Priority 14: Exit app (ctrl-c double-tap)
334+
// Priority 15: Exit app (ctrl-c double-tap)
321335
if (isCtrlC) {
322336
if (state.nextCtrlCWillExit) {
323337
return { type: 'exit-app' }

0 commit comments

Comments
 (0)