Skip to content

Commit 5315649

Browse files
committed
feat: prompt for low review feedback coverage
1 parent 7f60fe1 commit 5315649

File tree

3 files changed

+73
-1
lines changed

3 files changed

+73
-1
lines changed

TODO.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ This roadmap is derived from deep research into Greptile's public docs, blog, MC
9393
54. [ ] 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.
96-
57. [ ] Add a "train the reviewer" callout when thumbs coverage on a review is low.
96+
57. [x] Add a "train the reviewer" callout when thumbs coverage on a review is low.
9797
58. [ ] Add review-change comparisons so users can diff one review run against the next on the same PR.
9898
59. [ ] Add better surfacing for incremental PR reviews so users know when only the delta was reviewed.
9999
60. [ ] Add discussion workflows that can convert repeated human comments into candidate rules or context snippets.
@@ -165,4 +165,5 @@ This roadmap is derived from deep research into Greptile's public docs, blog, MC
165165
- [x] Add a blocker-only review mode that narrows large reviews to open Error and Warning findings.
166166
- [x] Add file-level readiness summaries to the review diff sidebar.
167167
- [x] Add visible feedback badges on comments so accepted and rejected states are not icon-only.
168+
- [x] Add a train-the-reviewer callout on review detail when thumbs coverage is low.
168169
- [ ] Commit and push each validated checkpoint before moving to the next epic.

web/src/pages/ReviewView.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import type { Comment, CommentLifecycleStatus, MergeReadiness, Severity, ReviewE
1313
type ViewMode = 'diff' | 'list'
1414

1515
const BLOCKING_SEVERITIES = new Set<Severity>(['Error', 'Warning'])
16+
const LOW_FEEDBACK_COVERAGE_THRESHOLD = 0.5
17+
const MIN_FEEDBACK_COVERAGE_FINDINGS = 3
1618

1719
type ReviewCommentFilters = {
1820
severityFilter: Set<Severity>
@@ -212,6 +214,11 @@ export function ReviewView() {
212214

213215
const openErrorCount = review.summary?.open_by_severity.Error ?? 0
214216
const openWarningCount = review.summary?.open_by_severity.Warning ?? 0
217+
const labeledFeedbackCount = comments.filter((comment) => comment.feedback === 'accept' || comment.feedback === 'reject').length
218+
const feedbackCoverage = comments.length > 0 ? labeledFeedbackCount / comments.length : 1
219+
const feedbackCoveragePercent = Math.round(feedbackCoverage * 100)
220+
const shouldShowFeedbackCallout = comments.length >= MIN_FEEDBACK_COVERAGE_FINDINGS
221+
&& feedbackCoverage < LOW_FEEDBACK_COVERAGE_THRESHOLD
215222

216223
return (
217224
<div className="flex h-full">
@@ -352,6 +359,30 @@ export function ReviewView() {
352359
</div>
353360
)}
354361

362+
{shouldShowFeedbackCallout && (
363+
<div className="px-3 py-3 border-b border-accent/20 bg-accent/5">
364+
<div className="flex items-start gap-3">
365+
<div className="mt-0.5 w-7 h-7 rounded-full bg-accent/10 flex items-center justify-center shrink-0">
366+
<MessageSquare size={14} className="text-accent" />
367+
</div>
368+
<div className="min-w-0 flex-1">
369+
<div className="text-[10px] font-semibold text-accent tracking-[0.08em] font-code uppercase">
370+
Train the reviewer
371+
</div>
372+
<p className="mt-1 text-[12px] text-text-primary leading-relaxed">
373+
{labeledFeedbackCount === 0
374+
? 'No thumbs recorded yet. Label findings below to train the reviewer.'
375+
: `You've labeled ${labeledFeedbackCount} of ${comments.length} findings. Add a few more thumbs so future reviews learn what to keep or suppress.`}
376+
</p>
377+
</div>
378+
<div className="shrink-0 text-right">
379+
<div className="text-[13px] font-semibold font-code text-accent">{feedbackCoveragePercent}%</div>
380+
<div className="text-[10px] text-text-muted">coverage</div>
381+
</div>
382+
</div>
383+
</div>
384+
)}
385+
355386
{/* Toolbar */}
356387
<div className="px-3 py-2 border-b border-border bg-surface flex items-center gap-2">
357388
{/* View mode toggle */}

web/src/pages/__tests__/ReviewView.test.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,44 @@ describe('ReviewView blocker mode', () => {
175175

176176
expect(screen.getByText('No open blockers remain in this review.')).toBeInTheDocument()
177177
})
178+
179+
it('shows a train-the-reviewer callout when thumbs coverage is low', () => {
180+
useReviewMock.mockReturnValue({ data: makeReview(), isLoading: false })
181+
182+
render(<ReviewView />)
183+
184+
expect(screen.getByText('Train the reviewer')).toBeInTheDocument()
185+
expect(screen.getByText('No thumbs recorded yet. Label findings below to train the reviewer.')).toBeInTheDocument()
186+
expect(screen.getByText('0%')).toBeInTheDocument()
187+
})
188+
189+
it('hides the train-the-reviewer callout when enough findings are labeled', () => {
190+
useReviewMock.mockReturnValue({
191+
data: makeReview({
192+
comments: [
193+
makeComment({ feedback: 'accept' }),
194+
makeComment({
195+
id: 'comment-2',
196+
file_path: 'src/b.ts',
197+
content: 'Informational note',
198+
severity: 'Info',
199+
category: 'Style',
200+
feedback: 'reject',
201+
}),
202+
makeComment({
203+
id: 'comment-3',
204+
file_path: 'src/b.ts',
205+
content: 'Resolved blocker',
206+
severity: 'Warning',
207+
status: 'Resolved',
208+
}),
209+
],
210+
}),
211+
isLoading: false,
212+
})
213+
214+
render(<ReviewView />)
215+
216+
expect(screen.queryByText('Train the reviewer')).not.toBeInTheDocument()
217+
})
178218
})

0 commit comments

Comments
 (0)