Skip to content

feat(ui): issue review banner with approve / request changes#4435

Open
ramonmatias19 wants to merge 1 commit intopaperclipai:masterfrom
ramonmatias19:feature/issue-review-banner
Open

feat(ui): issue review banner with approve / request changes#4435
ramonmatias19 wants to merge 1 commit intopaperclipai:masterfrom
ramonmatias19:feature/issue-review-banner

Conversation

@ramonmatias19
Copy link
Copy Markdown

Summary

When an issue is in in_review status and a review handler is provided, render an Aguardando revisão banner above the chat thread with two actions:

  • Approve (sets status=done)
  • Request changes (sets status=in_progress with a rejection reason)

Also: replies submitted via the composer automatically reopen the issue when it is in a closed status (done / cancelled) and no explicit reopen preference was passed.

Test plan

  • Put an issue in in_review — verify the banner appears
  • Approve — verify status flips to done and a comment is added
  • Request changes — verify status flips to in_progress and a comment is added
  • Reply to a done issue — verify it reopens automatically

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 24, 2026

Greptile Summary

This PR adds an in_review status banner to IssueChatThread with Approve and Request Changes actions, and auto-reopens closed issues when a reply is submitted. The implementation is mostly straightforward, but there is a meaningful gap: the PR description states that "Request changes" submits a rejection reason, yet onReviewRequestChanges has no parameter for one and the banner provides no UI to collect it.

  • Missing rejection reason: The callback signature () => Promise<void> and the button's direct invocation provide no way to pass a reason, contradicting the PR description's claim.
  • PR template not followed: The description is missing the required Thinking Path, Model Used section, and risks — per CONTRIBUTING.md, all sections of the PR template are required.
  • No screenshots: This is a visible UI change; before/after screenshots are required by CONTRIBUTING.md.

Confidence Score: 3/5

Not ready to merge — the rejection reason gap contradicts the stated spec, and the PR is missing required CONTRIBUTING.md sections and screenshots.

One P1 finding (rejection reason described in the PR spec but not implemented in the UI or callback contract) plus two process violations (missing PR template sections including Thinking Path and Model Used, no screenshots for a visual change) bring the score to 3. The auto-reopen and banner rendering logic themselves are correct.

ui/src/components/IssueChatThread.tsx — specifically the onReviewRequestChanges callback and the banner's button handler.

Important Files Changed

Filename Overview
ui/src/components/IssueChatThread.tsx Adds review banner with Approve/Request Changes actions and auto-reopen on reply to closed issues; missing rejection reason input despite PR description claiming it, plus shared loading state and silent error swallowing.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/components/IssueChatThread.tsx
Line: 2759-2771

Comment:
**Missing rejection reason UI**

The PR description states that "Request changes" sets `status=in_progress` **with a rejection reason**, but the `onReviewRequestChanges` callback signature is `() => Promise<void>` — no reason string is passed, and the button immediately fires the callback without prompting the user for input. Either the callback should accept a `reason: string` parameter (and the banner should include an inline text input or open a dialog to collect it), or the PR description overstates what this action does.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: ui/src/components/IssueChatThread.tsx
Line: 2397-2398

Comment:
**Shared loading state conflates both buttons**

`reviewSubmitting` is shared, so clicking either button sets both to disabled and shows "Enviando..." on each. A user who clicks "Solicitar alterações" will also see "Enviando..." on the "Aprovar" button, which is confusing. Consider tracking which action is in-flight (e.g., `reviewSubmitting: "approve" | "request-changes" | false`) and showing the spinner only on the clicked button.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: ui/src/components/IssueChatThread.tsx
Line: 2398

Comment:
**`showReviewBanner` evaluates to a function reference, not a boolean**

`const showReviewBanner = issueStatus === "in_review" && (onReviewApprove || onReviewRequestChanges)` — when `issueStatus === "in_review"` and `onReviewApprove` is defined, this evaluates to the function itself rather than `true`. React treats a function reference as a truthy value in `{showReviewBanner && (...)}` so the component renders correctly, but TypeScript's `--strictNullChecks` may emit a warning and it silently departs from conventional boolean-flag patterns. Prefer an explicit cast:

```suggestion
  const showReviewBanner = issueStatus === "in_review" && !!(onReviewApprove || onReviewRequestChanges);
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: ui/src/components/IssueChatThread.tsx
Line: 2741-2780

Comment:
**Silent failure on callback error**

Both `onClick` handlers swallow errors via `finally { setReviewSubmitting(false) }`. If `onReviewApprove()` or `onReviewRequestChanges()` rejects, the button simply returns to its idle state with no feedback to the user. Consider surfacing the error (e.g., via a toast or an inline error message) so the reviewer knows the action did not complete.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(ui): issue review banner with appro..." | Re-trigger Greptile

Comment on lines +2759 to +2771
<Button
size="sm"
disabled={reviewSubmitting}
onClick={async () => {
setReviewSubmitting(true);
try {
await onReviewApprove();
} finally {
setReviewSubmitting(false);
}
}}
>
<CheckCircle2 className="h-3.5 w-3.5 mr-1.5" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Missing rejection reason UI

The PR description states that "Request changes" sets status=in_progress with a rejection reason, but the onReviewRequestChanges callback signature is () => Promise<void> — no reason string is passed, and the button immediately fires the callback without prompting the user for input. Either the callback should accept a reason: string parameter (and the banner should include an inline text input or open a dialog to collect it), or the PR description overstates what this action does.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/components/IssueChatThread.tsx
Line: 2759-2771

Comment:
**Missing rejection reason UI**

The PR description states that "Request changes" sets `status=in_progress` **with a rejection reason**, but the `onReviewRequestChanges` callback signature is `() => Promise<void>` — no reason string is passed, and the button immediately fires the callback without prompting the user for input. Either the callback should accept a `reason: string` parameter (and the banner should include an inline text input or open a dialog to collect it), or the PR description overstates what this action does.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +2397 to +2398
const [reviewSubmitting, setReviewSubmitting] = useState(false);
const showReviewBanner = issueStatus === "in_review" && (onReviewApprove || onReviewRequestChanges);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Shared loading state conflates both buttons

reviewSubmitting is shared, so clicking either button sets both to disabled and shows "Enviando..." on each. A user who clicks "Solicitar alterações" will also see "Enviando..." on the "Aprovar" button, which is confusing. Consider tracking which action is in-flight (e.g., reviewSubmitting: "approve" | "request-changes" | false) and showing the spinner only on the clicked button.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/components/IssueChatThread.tsx
Line: 2397-2398

Comment:
**Shared loading state conflates both buttons**

`reviewSubmitting` is shared, so clicking either button sets both to disabled and shows "Enviando..." on each. A user who clicks "Solicitar alterações" will also see "Enviando..." on the "Aprovar" button, which is confusing. Consider tracking which action is in-flight (e.g., `reviewSubmitting: "approve" | "request-changes" | false`) and showing the spinner only on the clicked button.

How can I resolve this? If you propose a fix, please make it concise.

}: IssueChatThreadProps) {
const location = useLocation();
const [reviewSubmitting, setReviewSubmitting] = useState(false);
const showReviewBanner = issueStatus === "in_review" && (onReviewApprove || onReviewRequestChanges);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 showReviewBanner evaluates to a function reference, not a boolean

const showReviewBanner = issueStatus === "in_review" && (onReviewApprove || onReviewRequestChanges) — when issueStatus === "in_review" and onReviewApprove is defined, this evaluates to the function itself rather than true. React treats a function reference as a truthy value in {showReviewBanner && (...)} so the component renders correctly, but TypeScript's --strictNullChecks may emit a warning and it silently departs from conventional boolean-flag patterns. Prefer an explicit cast:

Suggested change
const showReviewBanner = issueStatus === "in_review" && (onReviewApprove || onReviewRequestChanges);
const showReviewBanner = issueStatus === "in_review" && !!(onReviewApprove || onReviewRequestChanges);
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/components/IssueChatThread.tsx
Line: 2398

Comment:
**`showReviewBanner` evaluates to a function reference, not a boolean**

`const showReviewBanner = issueStatus === "in_review" && (onReviewApprove || onReviewRequestChanges)` — when `issueStatus === "in_review"` and `onReviewApprove` is defined, this evaluates to the function itself rather than `true`. React treats a function reference as a truthy value in `{showReviewBanner && (...)}` so the component renders correctly, but TypeScript's `--strictNullChecks` may emit a warning and it silently departs from conventional boolean-flag patterns. Prefer an explicit cast:

```suggestion
  const showReviewBanner = issueStatus === "in_review" && !!(onReviewApprove || onReviewRequestChanges);
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +2741 to 2780
<Button
variant="outline"
size="sm"
disabled={reviewSubmitting}
onClick={async () => {
setReviewSubmitting(true);
try {
await onReviewRequestChanges();
} finally {
setReviewSubmitting(false);
}
}}
>
<XCircle className="h-3.5 w-3.5 mr-1.5" />
{reviewSubmitting ? "Enviando..." : "Solicitar alterações"}
</Button>
)}
{onReviewApprove && (
<Button
size="sm"
disabled={reviewSubmitting}
onClick={async () => {
setReviewSubmitting(true);
try {
await onReviewApprove();
} finally {
setReviewSubmitting(false);
}
}}
>
<CheckCircle2 className="h-3.5 w-3.5 mr-1.5" />
{reviewSubmitting ? "Enviando..." : "Aprovar"}
</Button>
)}
</div>
</div>
)}

{showComposer ? (
<div
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Silent failure on callback error

Both onClick handlers swallow errors via finally { setReviewSubmitting(false) }. If onReviewApprove() or onReviewRequestChanges() rejects, the button simply returns to its idle state with no feedback to the user. Consider surfacing the error (e.g., via a toast or an inline error message) so the reviewer knows the action did not complete.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/components/IssueChatThread.tsx
Line: 2741-2780

Comment:
**Silent failure on callback error**

Both `onClick` handlers swallow errors via `finally { setReviewSubmitting(false) }`. If `onReviewApprove()` or `onReviewRequestChanges()` rejects, the button simply returns to its idle state with no feedback to the user. Consider surfacing the error (e.g., via a toast or an inline error message) so the reviewer knows the action did not complete.

How can I resolve this? If you propose a fix, please make it concise.

When an issue is in the in_review status and a review handler is
provided, render an 'Aguardando revisão' banner above the chat thread
with two actions:
  - Approve (sets status=done)
  - Request changes (sets status=in_progress with a rejection reason)

Also: replies submitted via the composer automatically reopen the issue
when it is in a closed status (done/cancelled) and no explicit reopen
preference was passed.
@ramonmatias19 ramonmatias19 force-pushed the feature/issue-review-banner branch from 5ed3bf9 to b0d3f0b Compare April 24, 2026 20:25
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