Skip to content

Commit 987bf88

Browse files
committed
feat: add review keyboard shortcuts
1 parent dd461fa commit 987bf88

File tree

5 files changed

+251
-22
lines changed

5 files changed

+251
-22
lines changed

TODO.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ This roadmap is derived from deep research into Greptile's public docs, blog, MC
9090
51. [x] Add visible accepted/rejected/dismissed badges to comments throughout the UI, not just icon state.
9191
52. [x] Add comment grouping by unresolved, fixed, stale, and informational sections in `ReviewView`.
9292
53. [x] Add a "show only blockers" mode for large reviews.
93-
54. [ ] Add keyboard actions for thumbs, resolve, and jump-to-next-finding workflows.
93+
54. [x] Add keyboard actions for thumbs, resolve, and jump-to-next-finding workflows.
9494
55. [x] Add file-level readiness summaries in the diff sidebar.
9595
56. [x] Add lifecycle-aware PR summaries that explain what still blocks merge.
9696
57. [x] Add a "train the reviewer" callout when thumbs coverage on a review is low.
@@ -176,4 +176,5 @@ This roadmap is derived from deep research into Greptile's public docs, blog, MC
176176
- [x] Add a PR re-review API that reuses stored review metadata and posting policy.
177177
- [x] Add pattern repository resolution coverage for repo-local directories, Git sources, and broken-source skips.
178178
- [x] Group ReviewView list-mode findings into unresolved, fixed, stale, and informational sections.
179+
- [x] Add ReviewView keyboard shortcuts for next-finding navigation plus accept/reject/resolve actions.
179180
- [ ] Commit and push each validated checkpoint before moving to the next epic.

web/src/components/CommentCard.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ interface Props {
3232
variant?: 'card' | 'inline'
3333
onFeedback?: (action: 'accept' | 'reject') => void
3434
onLifecycleChange?: (status: 'open' | 'resolved' | 'dismissed') => void
35+
isActive?: boolean
36+
onActivate?: () => void
3537
}
3638

37-
export function CommentCard({ comment, variant = 'card', onFeedback, onLifecycleChange }: Props) {
39+
export function CommentCard({ comment, variant = 'card', onFeedback, onLifecycleChange, isActive = false, onActivate }: Props) {
3840
const [expanded, setExpanded] = useState(false)
3941
const [copied, setCopied] = useState(false)
4042
const accepted = comment.feedback === 'accept'
@@ -56,7 +58,14 @@ export function CommentCard({ comment, variant = 'card', onFeedback, onLifecycle
5658
: 'border border-border rounded-md bg-surface-2'
5759

5860
return (
59-
<div className={rootClass}>
61+
<div
62+
className={`${rootClass} ${isActive ? 'ring-1 ring-accent/50' : ''} focus:outline-none focus-visible:ring-1 focus-visible:ring-accent/50`}
63+
data-review-comment-card="true"
64+
data-comment-id={comment.id}
65+
tabIndex={0}
66+
onFocus={() => onActivate?.()}
67+
onMouseDown={() => onActivate?.()}
68+
>
6069
{/* Header */}
6170
<div className={`flex items-center gap-2 px-3 ${isInline ? 'py-1.5 border-b border-border-subtle' : 'py-2'}`}>
6271
<SeverityBadge severity={comment.severity} />

web/src/components/DiffViewer.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ interface Props {
1111
comments: Comment[]
1212
onFeedback?: (commentId: string, action: 'accept' | 'reject') => void
1313
onLifecycleChange?: (commentId: string, status: 'open' | 'resolved' | 'dismissed') => void
14+
activeCommentId?: string | null
15+
onActivateComment?: (commentId: string) => void
1416
}
1517

1618
const statusIcon = {
@@ -27,7 +29,7 @@ const statusColor = {
2729
modified: 'text-text-secondary',
2830
}
2931

30-
export function DiffViewer({ files, comments, onFeedback, onLifecycleChange }: Props) {
32+
export function DiffViewer({ files, comments, onFeedback, onLifecycleChange, activeCommentId = null, onActivateComment }: Props) {
3133
const [collapsedFiles, setCollapsedFiles] = useState<Set<string>>(new Set())
3234

3335
const commentsByFile = useMemo(() => {
@@ -108,6 +110,8 @@ export function DiffViewer({ files, comments, onFeedback, onLifecycleChange }: P
108110
commentsByLine={commentsByFile.get(file.path)}
109111
onFeedback={onFeedback}
110112
onLifecycleChange={onLifecycleChange}
113+
activeCommentId={activeCommentId}
114+
onActivateComment={onActivateComment}
111115
/>
112116
))}
113117
{file.hunks.length === 0 && (
@@ -124,12 +128,14 @@ export function DiffViewer({ files, comments, onFeedback, onLifecycleChange }: P
124128
)
125129
}
126130

127-
export function HunkView({ hunk, filePath, commentsByLine, onFeedback, onLifecycleChange }: {
131+
export function HunkView({ hunk, filePath, commentsByLine, onFeedback, onLifecycleChange, activeCommentId, onActivateComment }: {
128132
hunk: DiffHunk
129133
filePath: string
130134
commentsByLine?: Map<number, Comment[]>
131135
onFeedback?: (commentId: string, action: 'accept' | 'reject') => void
132136
onLifecycleChange?: (commentId: string, status: 'open' | 'resolved' | 'dismissed') => void
137+
activeCommentId?: string | null
138+
onActivateComment?: (commentId: string) => void
133139
}) {
134140
return (
135141
<div>
@@ -153,6 +159,8 @@ export function HunkView({ hunk, filePath, commentsByLine, onFeedback, onLifecyc
153159
filePath={filePath}
154160
onFeedback={onFeedback}
155161
onLifecycleChange={onLifecycleChange}
162+
activeCommentId={activeCommentId}
163+
onActivateComment={onActivateComment}
156164
/>
157165
)
158166
})}
@@ -162,12 +170,14 @@ export function HunkView({ hunk, filePath, commentsByLine, onFeedback, onLifecyc
162170
)
163171
}
164172

165-
export function LineRow({ line, comments, filePath, onFeedback, onLifecycleChange }: {
173+
export function LineRow({ line, comments, filePath, onFeedback, onLifecycleChange, activeCommentId, onActivateComment }: {
166174
line: DiffLine
167175
comments?: Comment[]
168176
filePath: string
169177
onFeedback?: (commentId: string, action: 'accept' | 'reject') => void
170178
onLifecycleChange?: (commentId: string, status: 'open' | 'resolved' | 'dismissed') => void
179+
activeCommentId?: string | null
180+
onActivateComment?: (commentId: string) => void
171181
}) {
172182
const bgClass =
173183
line.type === 'add' ? 'bg-diff-add-bg' :
@@ -218,6 +228,8 @@ export function LineRow({ line, comments, filePath, onFeedback, onLifecycleChang
218228
variant="inline"
219229
onFeedback={onFeedback ? (action) => onFeedback(c.id, action) : undefined}
220230
onLifecycleChange={onLifecycleChange ? (status) => onLifecycleChange(c.id, status) : undefined}
231+
isActive={activeCommentId === c.id}
232+
onActivate={onActivateComment ? () => onActivateComment(c.id) : undefined}
221233
/>
222234
))}
223235
</td>

0 commit comments

Comments
 (0)