|
10 | 10 | */ |
11 | 11 |
|
12 | 12 | import { use, useEffect, useState } from "react"; |
| 13 | +import { ConfirmationQueue, type Finding } from "@/components/confirmation"; |
13 | 14 | import { PageBody, PageHeader } from "@/components/page"; |
14 | 15 | import { |
15 | 16 | Badge, |
@@ -49,16 +50,7 @@ interface Snapshot { |
49 | 50 | }; |
50 | 51 | stages: { stage: string; status: string; attempt: number }[]; |
51 | 52 | notes: { kind: string; message: string; at: string }[]; |
52 | | - findings: { |
53 | | - id: string; |
54 | | - filePath: string; |
55 | | - lineStart: number; |
56 | | - severity: string; |
57 | | - issue: string; |
58 | | - comment: string; |
59 | | - status: string; |
60 | | - quotedCode: string; |
61 | | - }[]; |
| 53 | + findings: Finding[]; |
62 | 54 | running: boolean; |
63 | 55 | } |
64 | 56 |
|
@@ -120,6 +112,54 @@ export default function ReviewPage({ params }: { params: Promise<{ id: string }> |
120 | 112 | ); |
121 | 113 | const reported = findings.filter((finding) => finding.status !== "killed"); |
122 | 114 |
|
| 115 | + /** |
| 116 | + * The findings as a list you can only read. |
| 117 | + * |
| 118 | + * Used while a review is still running, and after it is complete. Between |
| 119 | + * those two the confirmation queue takes over, because that is the one |
| 120 | + * moment the findings are a thing to act on rather than a thing to look at. |
| 121 | + */ |
| 122 | + const renderReadOnly = () => |
| 123 | + reported.length === 0 ? ( |
| 124 | + <Empty title={snapshot.running ? "Nothing reported yet" : "No findings"}> |
| 125 | + {snapshot.running |
| 126 | + ? "Findings appear once the adversarial pass has run and each one has been checked against the file." |
| 127 | + : "Every hunk was accounted for and nothing survived verification."} |
| 128 | + </Empty> |
| 129 | + ) : ( |
| 130 | + <ul className="grid gap-3"> |
| 131 | + {reported.map((finding) => ( |
| 132 | + <li key={finding.id}> |
| 133 | + <Card className="p-4"> |
| 134 | + <div className="flex flex-wrap items-center gap-2"> |
| 135 | + <Badge tone={severityTone(finding.severity)}>{finding.severity}</Badge> |
| 136 | + {finding.status === "confirmed" ? <Badge tone="good">confirmed</Badge> : null} |
| 137 | + {finding.status === "dismissed" ? <Badge tone="neutral">dismissed</Badge> : null} |
| 138 | + {finding.status === "open_question" ? ( |
| 139 | + <Badge tone="question">open question</Badge> |
| 140 | + ) : null} |
| 141 | + <Mono className="text-xs text-[var(--color-ink-muted)]"> |
| 142 | + {finding.filePath}:{finding.lineStart} |
| 143 | + </Mono> |
| 144 | + </div> |
| 145 | + <p className="mt-2 font-medium">{finding.issue}</p> |
| 146 | + <p className="mt-1 text-sm text-[var(--color-ink-muted)]">{finding.comment}</p> |
| 147 | + {finding.dismissReason ? ( |
| 148 | + <p className="mt-1 text-xs text-[var(--color-ink-faint)]"> |
| 149 | + Dismissed: {finding.dismissReason} |
| 150 | + </p> |
| 151 | + ) : null} |
| 152 | + {finding.quotedCode ? ( |
| 153 | + <pre className="mt-3 overflow-x-auto rounded border border-[var(--color-border)] bg-[var(--color-surface-sunken)] p-3 text-xs"> |
| 154 | + <code className="font-[family-name:var(--font-mono)]">{finding.quotedCode}</code> |
| 155 | + </pre> |
| 156 | + ) : null} |
| 157 | + </Card> |
| 158 | + </li> |
| 159 | + ))} |
| 160 | + </ul> |
| 161 | + ); |
| 162 | + |
123 | 163 | return ( |
124 | 164 | <> |
125 | 165 | <PageHeader |
@@ -181,45 +221,19 @@ export default function ReviewPage({ params }: { params: Promise<{ id: string }> |
181 | 221 | </ol> |
182 | 222 | </Card> |
183 | 223 |
|
184 | | - <h2 className="mb-3 text-sm font-semibold"> |
185 | | - Findings{reported.length > 0 ? ` (${reported.length})` : ""} |
186 | | - </h2> |
187 | | - |
188 | | - {reported.length === 0 ? ( |
189 | | - <Empty title={snapshot.running ? "Nothing reported yet" : "No findings"}> |
190 | | - {snapshot.running |
191 | | - ? "Findings appear once the adversarial pass has run and each one has been checked against the file." |
192 | | - : "Every hunk was accounted for and nothing survived verification."} |
193 | | - </Empty> |
| 224 | + {review.status === "awaiting_confirmation" && reported.length > 0 ? ( |
| 225 | + <ConfirmationQueue |
| 226 | + reviewId={id} |
| 227 | + findings={findings} |
| 228 | + onChanged={() => void reload()} |
| 229 | + /> |
194 | 230 | ) : ( |
195 | | - <ul className="grid gap-3"> |
196 | | - {reported.map((finding) => ( |
197 | | - <li key={finding.id}> |
198 | | - <Card className="p-4"> |
199 | | - <div className="flex flex-wrap items-center gap-2"> |
200 | | - <Badge tone={severityTone(finding.severity)}>{finding.severity}</Badge> |
201 | | - {finding.status === "open_question" ? ( |
202 | | - <Badge tone="question">open question</Badge> |
203 | | - ) : null} |
204 | | - <Mono className="text-xs text-[var(--color-ink-muted)]"> |
205 | | - {finding.filePath}:{finding.lineStart} |
206 | | - </Mono> |
207 | | - </div> |
208 | | - <p className="mt-2 font-medium">{finding.issue}</p> |
209 | | - <p className="mt-1 text-sm text-[var(--color-ink-muted)]"> |
210 | | - {finding.comment} |
211 | | - </p> |
212 | | - {finding.quotedCode ? ( |
213 | | - <pre className="mt-3 overflow-x-auto rounded border border-[var(--color-border)] bg-[var(--color-surface-sunken)] p-3 text-xs"> |
214 | | - <code className="font-[family-name:var(--font-mono)]"> |
215 | | - {finding.quotedCode} |
216 | | - </code> |
217 | | - </pre> |
218 | | - ) : null} |
219 | | - </Card> |
220 | | - </li> |
221 | | - ))} |
222 | | - </ul> |
| 231 | + <> |
| 232 | + <h2 className="mb-3 text-sm font-semibold"> |
| 233 | + Findings{reported.length > 0 ? ` (${reported.length})` : ""} |
| 234 | + </h2> |
| 235 | + {renderReadOnly()} |
| 236 | + </> |
223 | 237 | )} |
224 | 238 | </div> |
225 | 239 |
|
|
0 commit comments