@cat-factory/app 0.147.1 → 0.147.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/board/nodes/TaskPipelineMini.vue +23 -2
- package/app/components/pipeline/PipelineProgress.vue +25 -1
- package/app/components/prReview/PrReviewPhaseBadge.vue +74 -0
- package/app/components/prReview/PrReviewWindow.vue +430 -403
- package/app/stores/personalSubscriptions.spec.ts +57 -6
- package/app/stores/personalSubscriptions.ts +72 -11
- package/app/utils/prReviewProgress.spec.ts +63 -12
- package/app/utils/prReviewProgress.ts +70 -8
- package/i18n/locales/de.json +11 -3
- package/i18n/locales/en.json +11 -3
- package/i18n/locales/es.json +11 -3
- package/i18n/locales/fr.json +11 -3
- package/i18n/locales/he.json +11 -3
- package/i18n/locales/it.json +11 -3
- package/i18n/locales/ja.json +11 -3
- package/i18n/locales/pl.json +11 -3
- package/i18n/locales/tr.json +11 -3
- package/i18n/locales/uk.json +11 -3
- package/package.json +1 -1
|
@@ -21,8 +21,9 @@ import type {
|
|
|
21
21
|
StepSubtasks,
|
|
22
22
|
} from '~/types/execution'
|
|
23
23
|
import { subtaskIconClass } from '~/utils/pipelineRender'
|
|
24
|
-
import { activeChunkLabels, chunkReviewPercent,
|
|
24
|
+
import { activeChunkLabels, chunkReviewPercent, hasNoSlicePlan } from '~/utils/prReviewProgress'
|
|
25
25
|
import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
|
|
26
|
+
import StepRunMeta from '~/components/panels/StepRunMeta.vue'
|
|
26
27
|
|
|
27
28
|
const execution = useExecutionStore()
|
|
28
29
|
const board = useBoardStore()
|
|
@@ -54,15 +55,18 @@ const awaiting = computed(() => status.value === 'awaiting_selection')
|
|
|
54
55
|
const challenging = computed(() => status.value === 'challenging')
|
|
55
56
|
// The reviewer's live todo list while it works, streamed onto the step. Its entries are the
|
|
56
57
|
// cohesive slices/chunks the agent grouped the diff into (plus a final "aggregate" step). The
|
|
57
|
-
// derivation of the
|
|
58
|
-
// (pure + unit-tested); see that module for the
|
|
58
|
+
// derivation of the `reviewing`-phase sub-states from it lives in `~/utils/prReviewProgress`
|
|
59
|
+
// (pure + unit-tested); see that module for the has-a-plan signal.
|
|
59
60
|
const subtasks = computed<StepSubtasks | null>(() => step.value?.subtasks ?? null)
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
63
|
+
* Planning sub-phase: no per-slice todo plan has been reported yet. This is NEUTRAL — the
|
|
64
|
+
* reviewer may still be grouping the diff, OR it may be reviewing via parallel subagents that
|
|
65
|
+
* never write a parent todo plan (ADR 0026 D2.2). Either way we must NOT claim a specific
|
|
66
|
+
* "slicing" phase; we show a neutral "reviewing…" state and switch to the per-slice list the
|
|
67
|
+
* moment a plan exists.
|
|
64
68
|
*/
|
|
65
|
-
const
|
|
69
|
+
const planning = computed(() => status.value === 'reviewing' && hasNoSlicePlan(subtasks.value))
|
|
66
70
|
|
|
67
71
|
/** Chunk-review completion, clamped 0..100 for the progress bar. */
|
|
68
72
|
const chunkPercent = computed(() => chunkReviewPercent(subtasks.value))
|
|
@@ -253,446 +257,469 @@ async function onDismiss(id: string): Promise<void> {
|
|
|
253
257
|
</a>
|
|
254
258
|
</template>
|
|
255
259
|
|
|
256
|
-
<div class="min-h-0 flex-1
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
data-testid="pr-review-reviewing"
|
|
263
|
-
class="flex h-full flex-col"
|
|
264
|
-
>
|
|
265
|
-
<!-- SLICING: no todo list yet — the reviewer is still grouping the diff into chunks. -->
|
|
260
|
+
<div class="flex min-h-0 flex-1">
|
|
261
|
+
<div class="min-w-0 flex-1 overflow-y-auto px-5 py-4">
|
|
262
|
+
<!-- Reviewing: the read-only reviewer is still working. Two sub-states, told apart by
|
|
263
|
+
whether a per-slice todo plan exists — a NEUTRAL "planning" state (no plan reported yet;
|
|
264
|
+
may be grouping OR reviewing via subagents that write no parent plan) vs the per-slice
|
|
265
|
+
list. The copy never asserts a specific "slicing" phase from an empty todo list. -->
|
|
266
266
|
<div
|
|
267
|
-
v-if="
|
|
268
|
-
data-testid="pr-review-
|
|
269
|
-
class="flex h-full flex-col
|
|
267
|
+
v-if="status === 'reviewing'"
|
|
268
|
+
data-testid="pr-review-reviewing"
|
|
269
|
+
class="flex h-full flex-col"
|
|
270
270
|
>
|
|
271
|
-
|
|
272
|
-
<p class="text-sm text-slate-200">{{ t('prReview.reviewing.slicing.title') }}</p>
|
|
273
|
-
<p class="max-w-sm text-[11px] text-slate-500">
|
|
274
|
-
{{ t('prReview.reviewing.slicing.hint') }}
|
|
275
|
-
</p>
|
|
276
|
-
</div>
|
|
277
|
-
|
|
278
|
-
<!-- REVIEWING: slicing is done — show every chunk with its status + which are active now. -->
|
|
279
|
-
<div v-else data-testid="pr-review-reviewing-chunks" class="py-2">
|
|
280
|
-
<div class="mb-1 flex items-center gap-2 text-sm text-slate-200">
|
|
281
|
-
<UIcon
|
|
282
|
-
name="i-lucide-loader-circle"
|
|
283
|
-
class="h-4 w-4 shrink-0 animate-spin text-indigo-300"
|
|
284
|
-
/>
|
|
285
|
-
<span>{{ t('prReview.reviewing.reviewingChunks.title') }}</span>
|
|
286
|
-
</div>
|
|
287
|
-
<p class="mb-3 text-[11px] text-slate-500">
|
|
288
|
-
{{ t('prReview.reviewing.reviewingChunks.hint') }}
|
|
289
|
-
</p>
|
|
290
|
-
|
|
291
|
-
<div class="flex items-center justify-between text-[11px] text-slate-400">
|
|
292
|
-
<span data-testid="pr-review-chunk-count">
|
|
293
|
-
{{
|
|
294
|
-
t('prReview.reviewing.chunks', {
|
|
295
|
-
completed: subtasks!.completed,
|
|
296
|
-
total: subtasks!.total,
|
|
297
|
-
})
|
|
298
|
-
}}
|
|
299
|
-
</span>
|
|
300
|
-
<span v-if="subtasks!.inProgress > 0" class="text-indigo-300">
|
|
301
|
-
{{ t('prReview.reviewing.inProgress', { count: subtasks!.inProgress }) }}
|
|
302
|
-
</span>
|
|
303
|
-
</div>
|
|
304
|
-
<div class="mt-1 h-1.5 overflow-hidden rounded-full bg-slate-700/60">
|
|
305
|
-
<div
|
|
306
|
-
class="h-full rounded-full bg-indigo-400 transition-all duration-500"
|
|
307
|
-
:style="{ width: `${chunkPercent}%` }"
|
|
308
|
-
/>
|
|
309
|
-
</div>
|
|
310
|
-
|
|
311
|
-
<!-- The chunk(s) being actively reviewed right now, called out on their own. -->
|
|
271
|
+
<!-- PLANNING: no per-slice plan yet — a neutral "reviewing…" state, not a "slicing" claim. -->
|
|
312
272
|
<div
|
|
313
|
-
v-if="
|
|
314
|
-
data-testid="pr-review-
|
|
315
|
-
class="
|
|
273
|
+
v-if="planning"
|
|
274
|
+
data-testid="pr-review-planning"
|
|
275
|
+
class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
|
|
316
276
|
>
|
|
317
|
-
<
|
|
318
|
-
|
|
277
|
+
<UIcon name="i-lucide-loader-circle" class="h-8 w-8 animate-spin opacity-60" />
|
|
278
|
+
<p class="text-sm text-slate-200">{{ t('prReview.reviewing.planning.title') }}</p>
|
|
279
|
+
<p class="max-w-sm text-[11px] text-slate-500">
|
|
280
|
+
{{ t('prReview.reviewing.planning.hint') }}
|
|
319
281
|
</p>
|
|
320
|
-
<ul class="space-y-1">
|
|
321
|
-
<li
|
|
322
|
-
v-for="(label, i) in activeChunks"
|
|
323
|
-
:key="i"
|
|
324
|
-
class="flex items-start gap-1.5 text-[12px] text-slate-100"
|
|
325
|
-
>
|
|
326
|
-
<UIcon
|
|
327
|
-
name="i-lucide-loader-circle"
|
|
328
|
-
class="mt-0.5 h-3.5 w-3.5 shrink-0 animate-spin text-indigo-300"
|
|
329
|
-
/>
|
|
330
|
-
<span class="min-w-0">{{ label }}</span>
|
|
331
|
-
</li>
|
|
332
|
-
</ul>
|
|
333
282
|
</div>
|
|
334
283
|
|
|
335
|
-
<!--
|
|
336
|
-
<
|
|
337
|
-
<
|
|
338
|
-
|
|
284
|
+
<!-- REVIEWING: a per-slice plan exists — show every slice with its status + which are active now. -->
|
|
285
|
+
<div v-else data-testid="pr-review-reviewing-chunks" class="py-2">
|
|
286
|
+
<div class="mb-1 flex items-center gap-2 text-sm text-slate-200">
|
|
287
|
+
<UIcon
|
|
288
|
+
name="i-lucide-loader-circle"
|
|
289
|
+
class="h-4 w-4 shrink-0 animate-spin text-indigo-300"
|
|
290
|
+
/>
|
|
291
|
+
<span>{{ t('prReview.reviewing.reviewingChunks.title') }}</span>
|
|
292
|
+
</div>
|
|
293
|
+
<p class="mb-3 text-[11px] text-slate-500">
|
|
294
|
+
{{ t('prReview.reviewing.reviewingChunks.hint') }}
|
|
339
295
|
</p>
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
296
|
+
|
|
297
|
+
<div class="flex items-center justify-between text-[11px] text-slate-400">
|
|
298
|
+
<span data-testid="pr-review-chunk-count">
|
|
299
|
+
{{
|
|
300
|
+
t('prReview.reviewing.chunks', {
|
|
301
|
+
completed: subtasks!.completed,
|
|
302
|
+
total: subtasks!.total,
|
|
303
|
+
})
|
|
304
|
+
}}
|
|
305
|
+
</span>
|
|
306
|
+
<span v-if="subtasks!.inProgress > 0" class="text-indigo-300">
|
|
307
|
+
{{ t('prReview.reviewing.inProgress', { count: subtasks!.inProgress }) }}
|
|
308
|
+
</span>
|
|
309
|
+
</div>
|
|
310
|
+
<div class="mt-1 h-1.5 overflow-hidden rounded-full bg-slate-700/60">
|
|
311
|
+
<div
|
|
312
|
+
class="h-full rounded-full bg-indigo-400 transition-all duration-500"
|
|
313
|
+
:style="{ width: `${chunkPercent}%` }"
|
|
314
|
+
/>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
<!-- The chunk(s) being actively reviewed right now, called out on their own. -->
|
|
318
|
+
<div
|
|
319
|
+
v-if="activeChunks.length"
|
|
320
|
+
data-testid="pr-review-active-chunks"
|
|
321
|
+
class="mt-3 rounded-lg border border-indigo-500/30 bg-indigo-500/5 px-2.5 py-2"
|
|
322
|
+
>
|
|
323
|
+
<p class="mb-1 text-[10px] font-semibold uppercase tracking-wide text-indigo-300">
|
|
324
|
+
{{ t('prReview.reviewing.activeHeading') }}
|
|
325
|
+
</p>
|
|
326
|
+
<ul class="space-y-1">
|
|
327
|
+
<li
|
|
328
|
+
v-for="(label, i) in activeChunks"
|
|
329
|
+
:key="i"
|
|
330
|
+
class="flex items-start gap-1.5 text-[12px] text-slate-100"
|
|
362
331
|
>
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
class="
|
|
368
|
-
|
|
332
|
+
<UIcon
|
|
333
|
+
name="i-lucide-loader-circle"
|
|
334
|
+
class="mt-0.5 h-3.5 w-3.5 shrink-0 animate-spin text-indigo-300"
|
|
335
|
+
/>
|
|
336
|
+
<span class="min-w-0">{{ label }}</span>
|
|
337
|
+
</li>
|
|
338
|
+
</ul>
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
<!-- Every chunk with its explicit status (Reviewed / Reviewing… / Queued). -->
|
|
342
|
+
<template v-if="subtasks!.items?.length">
|
|
343
|
+
<p
|
|
344
|
+
class="mb-1.5 mt-3 text-[10px] font-semibold uppercase tracking-wide text-slate-400"
|
|
345
|
+
>
|
|
346
|
+
{{ t('prReview.reviewing.chunksHeading') }}
|
|
347
|
+
</p>
|
|
348
|
+
<ul class="space-y-1.5" data-testid="pr-review-chunks">
|
|
349
|
+
<li
|
|
350
|
+
v-for="(item, i) in subtasks!.items"
|
|
351
|
+
:key="i"
|
|
352
|
+
data-testid="pr-review-chunk"
|
|
353
|
+
class="flex items-center gap-1.5 text-[12px]"
|
|
354
|
+
:class="
|
|
355
|
+
item.status === 'completed'
|
|
356
|
+
? 'text-slate-500'
|
|
357
|
+
: item.status === 'in_progress'
|
|
358
|
+
? 'text-slate-100'
|
|
359
|
+
: 'text-slate-400'
|
|
360
|
+
"
|
|
369
361
|
>
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
362
|
+
<UIcon
|
|
363
|
+
:name="ITEM_ICON[item.status]"
|
|
364
|
+
class="h-3.5 w-3.5 shrink-0"
|
|
365
|
+
:class="subtaskIconClass(item.status, false)"
|
|
366
|
+
/>
|
|
367
|
+
<span
|
|
368
|
+
class="min-w-0 flex-1 truncate"
|
|
369
|
+
:class="item.status === 'completed' ? 'line-through' : ''"
|
|
370
|
+
>
|
|
371
|
+
{{ item.label }}
|
|
372
|
+
</span>
|
|
373
|
+
<span
|
|
374
|
+
data-testid="pr-review-chunk-status"
|
|
375
|
+
class="shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium uppercase"
|
|
376
|
+
:class="CHUNK_STATUS_CLASS[item.status]"
|
|
377
|
+
>
|
|
378
|
+
{{ chunkStatusLabel(item.status) }}
|
|
379
|
+
</span>
|
|
380
|
+
</li>
|
|
381
|
+
</ul>
|
|
382
|
+
</template>
|
|
383
|
+
</div>
|
|
375
384
|
</div>
|
|
376
|
-
</div>
|
|
377
|
-
|
|
378
|
-
<!-- A resolution is executing: the Fixer is committing / comments are being posted. -->
|
|
379
|
-
<div
|
|
380
|
-
v-else-if="working"
|
|
381
|
-
data-testid="pr-review-working"
|
|
382
|
-
class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
|
|
383
|
-
>
|
|
384
|
-
<UIcon name="i-lucide-loader-circle" class="h-8 w-8 animate-spin opacity-60" />
|
|
385
|
-
<p class="text-sm">
|
|
386
|
-
{{ status === 'fixing' ? t('prReview.fixing.title') : t('prReview.posting.title') }}
|
|
387
|
-
</p>
|
|
388
|
-
<p class="max-w-sm text-[11px] text-slate-500">
|
|
389
|
-
{{ status === 'fixing' ? t('prReview.fixing.hint') : t('prReview.posting.hint') }}
|
|
390
|
-
</p>
|
|
391
|
-
</div>
|
|
392
|
-
|
|
393
|
-
<template v-else>
|
|
394
|
-
<p
|
|
395
|
-
v-if="prReview.error"
|
|
396
|
-
class="mb-3 rounded-md bg-rose-500/10 px-3 py-2 text-[12px] text-rose-300"
|
|
397
|
-
>
|
|
398
|
-
{{ prReview.error }}
|
|
399
|
-
</p>
|
|
400
385
|
|
|
401
|
-
<!--
|
|
402
|
-
which failed + why, and how many findings were folded into the summary because their
|
|
403
|
-
line isn't in the PR diff. Surfaced so a partial/failed post is legible + retryable. -->
|
|
386
|
+
<!-- A resolution is executing: the Fixer is committing / comments are being posted. -->
|
|
404
387
|
<div
|
|
405
|
-
v-if="
|
|
406
|
-
data-testid="pr-review-
|
|
407
|
-
class="
|
|
408
|
-
:class="
|
|
409
|
-
postReport.failures.length > 0 || postReport.bodyPosted === false
|
|
410
|
-
? 'border-amber-500/40 bg-amber-500/10 text-amber-200'
|
|
411
|
-
: 'border-emerald-500/40 bg-emerald-500/10 text-emerald-200'
|
|
412
|
-
"
|
|
388
|
+
v-else-if="working"
|
|
389
|
+
data-testid="pr-review-working"
|
|
390
|
+
class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
|
|
413
391
|
>
|
|
414
|
-
<
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
postReport.failures.length > 0 || postReport.bodyPosted === false
|
|
418
|
-
? 'i-lucide-alert-triangle'
|
|
419
|
-
: 'i-lucide-check-circle-2'
|
|
420
|
-
"
|
|
421
|
-
class="h-4 w-4 shrink-0"
|
|
422
|
-
/>
|
|
423
|
-
<span>{{ t('prReview.postReport.heading') }}</span>
|
|
424
|
-
</div>
|
|
425
|
-
<p data-testid="pr-review-post-count">
|
|
426
|
-
{{
|
|
427
|
-
t('prReview.postReport.posted', {
|
|
428
|
-
posted: postReport.posted,
|
|
429
|
-
attempted: postReport.attempted,
|
|
430
|
-
})
|
|
431
|
-
}}
|
|
432
|
-
</p>
|
|
433
|
-
<p v-if="postReport.folded > 0" class="mt-0.5 opacity-90">
|
|
434
|
-
{{ t('prReview.postReport.folded', { count: postReport.folded }) }}
|
|
392
|
+
<UIcon name="i-lucide-loader-circle" class="h-8 w-8 animate-spin opacity-60" />
|
|
393
|
+
<p class="text-sm">
|
|
394
|
+
{{ status === 'fixing' ? t('prReview.fixing.title') : t('prReview.posting.title') }}
|
|
435
395
|
</p>
|
|
436
|
-
<
|
|
437
|
-
|
|
438
|
-
<ul class="mt-0.5 space-y-0.5" data-testid="pr-review-post-failures">
|
|
439
|
-
<li v-for="f in postReport.failures" :key="f.findingId" class="flex gap-1.5">
|
|
440
|
-
<code class="shrink-0 text-amber-100"
|
|
441
|
-
>{{ f.path }}<template v-if="f.line != null">:{{ f.line }}</template></code
|
|
442
|
-
>
|
|
443
|
-
<span class="opacity-90">— {{ f.reason }}</span>
|
|
444
|
-
</li>
|
|
445
|
-
</ul>
|
|
446
|
-
</template>
|
|
447
|
-
<p v-if="postReport.bodyPosted === false && postReport.bodyError" class="mt-1.5">
|
|
448
|
-
{{ t('prReview.postReport.bodyError', { error: postReport.bodyError }) }}
|
|
396
|
+
<p class="max-w-sm text-[11px] text-slate-500">
|
|
397
|
+
{{ status === 'fixing' ? t('prReview.fixing.hint') : t('prReview.posting.hint') }}
|
|
449
398
|
</p>
|
|
450
399
|
</div>
|
|
451
400
|
|
|
452
|
-
<!-- The reviewer's overall assessment. -->
|
|
453
|
-
<p
|
|
454
|
-
v-if="state?.summary"
|
|
455
|
-
class="mb-3 rounded-md bg-slate-800/50 px-3 py-2 text-[12px] text-slate-300"
|
|
456
|
-
>
|
|
457
|
-
<span class="text-slate-500">{{ t('prReview.summaryLabel') }}</span>
|
|
458
|
-
{{ state.summary }}
|
|
459
|
-
</p>
|
|
460
|
-
|
|
461
|
-
<!-- A clean PR / resolved review with no findings. -->
|
|
462
|
-
<div
|
|
463
|
-
v-if="findings.length === 0"
|
|
464
|
-
class="rounded-xl border border-slate-800 bg-slate-900/60 px-4 py-6 text-center text-[13px] text-slate-300"
|
|
465
|
-
>
|
|
466
|
-
{{ t('prReview.noFindings') }}
|
|
467
|
-
</div>
|
|
468
|
-
|
|
469
401
|
<template v-else>
|
|
470
|
-
|
|
402
|
+
<p
|
|
403
|
+
v-if="prReview.error"
|
|
404
|
+
class="mb-3 rounded-md bg-rose-500/10 px-3 py-2 text-[12px] text-rose-300"
|
|
405
|
+
>
|
|
406
|
+
{{ prReview.error }}
|
|
407
|
+
</p>
|
|
408
|
+
|
|
409
|
+
<!-- The outcome of the most recent `post` attempt: how many of how many comments landed,
|
|
410
|
+
which failed + why, and how many findings were folded into the summary because their
|
|
411
|
+
line isn't in the PR diff. Surfaced so a partial/failed post is legible + retryable. -->
|
|
471
412
|
<div
|
|
472
|
-
v-if="
|
|
473
|
-
data-testid="pr-review-
|
|
474
|
-
class="mb-3
|
|
413
|
+
v-if="postReport"
|
|
414
|
+
data-testid="pr-review-post-report"
|
|
415
|
+
class="mb-3 rounded-lg border px-3 py-2 text-[12px]"
|
|
416
|
+
:class="
|
|
417
|
+
postReport.failures.length > 0 || postReport.bodyPosted === false
|
|
418
|
+
? 'border-amber-500/40 bg-amber-500/10 text-amber-200'
|
|
419
|
+
: 'border-emerald-500/40 bg-emerald-500/10 text-emerald-200'
|
|
420
|
+
"
|
|
475
421
|
>
|
|
476
|
-
<
|
|
477
|
-
|
|
422
|
+
<div class="mb-1 flex items-center gap-1.5 font-medium">
|
|
423
|
+
<UIcon
|
|
424
|
+
:name="
|
|
425
|
+
postReport.failures.length > 0 || postReport.bodyPosted === false
|
|
426
|
+
? 'i-lucide-alert-triangle'
|
|
427
|
+
: 'i-lucide-check-circle-2'
|
|
428
|
+
"
|
|
429
|
+
class="h-4 w-4 shrink-0"
|
|
430
|
+
/>
|
|
431
|
+
<span>{{ t('prReview.postReport.heading') }}</span>
|
|
432
|
+
</div>
|
|
433
|
+
<p data-testid="pr-review-post-count">
|
|
434
|
+
{{
|
|
435
|
+
t('prReview.postReport.posted', {
|
|
436
|
+
posted: postReport.posted,
|
|
437
|
+
attempted: postReport.attempted,
|
|
438
|
+
})
|
|
439
|
+
}}
|
|
440
|
+
</p>
|
|
441
|
+
<p v-if="postReport.folded > 0" class="mt-0.5 opacity-90">
|
|
442
|
+
{{ t('prReview.postReport.folded', { count: postReport.folded }) }}
|
|
443
|
+
</p>
|
|
444
|
+
<template v-if="postReport.failures.length > 0">
|
|
445
|
+
<p class="mt-1.5 font-medium">{{ t('prReview.postReport.failuresHeading') }}</p>
|
|
446
|
+
<ul class="mt-0.5 space-y-0.5" data-testid="pr-review-post-failures">
|
|
447
|
+
<li v-for="f in postReport.failures" :key="f.findingId" class="flex gap-1.5">
|
|
448
|
+
<code class="shrink-0 text-amber-100"
|
|
449
|
+
>{{ f.path }}<template v-if="f.line != null">:{{ f.line }}</template></code
|
|
450
|
+
>
|
|
451
|
+
<span class="opacity-90">— {{ f.reason }}</span>
|
|
452
|
+
</li>
|
|
453
|
+
</ul>
|
|
454
|
+
</template>
|
|
455
|
+
<p v-if="postReport.bodyPosted === false && postReport.bodyError" class="mt-1.5">
|
|
456
|
+
{{ t('prReview.postReport.bodyError', { error: postReport.bodyError }) }}
|
|
457
|
+
</p>
|
|
478
458
|
</div>
|
|
479
459
|
|
|
480
|
-
<!--
|
|
481
|
-
<
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
<
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
460
|
+
<!-- The reviewer's overall assessment. -->
|
|
461
|
+
<p
|
|
462
|
+
v-if="state?.summary"
|
|
463
|
+
class="mb-3 rounded-md bg-slate-800/50 px-3 py-2 text-[12px] text-slate-300"
|
|
464
|
+
>
|
|
465
|
+
<span class="text-slate-500">{{ t('prReview.summaryLabel') }}</span>
|
|
466
|
+
{{ state.summary }}
|
|
467
|
+
</p>
|
|
468
|
+
|
|
469
|
+
<!-- A clean PR / resolved review with no findings. -->
|
|
470
|
+
<div
|
|
471
|
+
v-if="findings.length === 0"
|
|
472
|
+
class="rounded-xl border border-slate-800 bg-slate-900/60 px-4 py-6 text-center text-[13px] text-slate-300"
|
|
473
|
+
>
|
|
474
|
+
{{ t('prReview.noFindings') }}
|
|
491
475
|
</div>
|
|
492
476
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
<
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
{{ g.rationale }}
|
|
500
|
-
</p>
|
|
501
|
-
<article
|
|
502
|
-
v-for="f in g.items"
|
|
503
|
-
:key="f.id"
|
|
504
|
-
data-testid="pr-review-finding"
|
|
505
|
-
class="mb-1.5 rounded-xl border px-3 py-2 transition"
|
|
506
|
-
:class="[
|
|
507
|
-
awaiting && selected.has(f.id) && !isRetracted(f)
|
|
508
|
-
? 'border-indigo-500/60 bg-indigo-500/5'
|
|
509
|
-
: 'border-slate-800 bg-slate-900/60',
|
|
510
|
-
isRetracted(f) ? 'opacity-60' : '',
|
|
511
|
-
]"
|
|
477
|
+
<template v-else>
|
|
478
|
+
<!-- A challenge is in flight: the Challenge Investigator is re-examining a finding. -->
|
|
479
|
+
<div
|
|
480
|
+
v-if="challenging"
|
|
481
|
+
data-testid="pr-review-challenging"
|
|
482
|
+
class="mb-3 flex items-center gap-2 rounded-md border border-indigo-500/40 bg-indigo-500/10 px-3 py-2 text-[12px] text-indigo-200"
|
|
512
483
|
>
|
|
513
|
-
<
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
484
|
+
<UIcon name="i-lucide-loader-circle" class="h-4 w-4 shrink-0 animate-spin" />
|
|
485
|
+
<span>{{ t('prReview.challenge.investigatingBanner') }}</span>
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
<!-- Selection toolbar -->
|
|
489
|
+
<div v-if="awaiting" class="mb-2 flex items-center gap-3 text-[11px] text-slate-400">
|
|
490
|
+
<span data-testid="pr-review-selected-count">
|
|
491
|
+
{{ t('prReview.selectedCount', { count: activeSelectedIds.length }) }}
|
|
492
|
+
</span>
|
|
493
|
+
<button class="text-indigo-300 hover:underline" @click="selectAll">
|
|
494
|
+
{{ t('prReview.selectAll') }}
|
|
495
|
+
</button>
|
|
496
|
+
<button class="text-indigo-300 hover:underline" @click="clearAll">
|
|
497
|
+
{{ t('prReview.clear') }}
|
|
498
|
+
</button>
|
|
499
|
+
</div>
|
|
500
|
+
|
|
501
|
+
<!-- Findings grouped by slice -->
|
|
502
|
+
<section v-for="g in groups" :key="g.id" class="mb-4">
|
|
503
|
+
<h3 class="mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
504
|
+
{{ g.title }}
|
|
505
|
+
</h3>
|
|
506
|
+
<p v-if="g.rationale" class="mb-1.5 text-[11px] text-slate-500">
|
|
507
|
+
{{ g.rationale }}
|
|
508
|
+
</p>
|
|
509
|
+
<article
|
|
510
|
+
v-for="f in g.items"
|
|
511
|
+
:key="f.id"
|
|
512
|
+
data-testid="pr-review-finding"
|
|
513
|
+
class="mb-1.5 rounded-xl border px-3 py-2 transition"
|
|
514
|
+
:class="[
|
|
515
|
+
awaiting && selected.has(f.id) && !isRetracted(f)
|
|
516
|
+
? 'border-indigo-500/60 bg-indigo-500/5'
|
|
517
|
+
: 'border-slate-800 bg-slate-900/60',
|
|
518
|
+
isRetracted(f) ? 'opacity-60' : '',
|
|
519
|
+
]"
|
|
520
|
+
>
|
|
521
|
+
<div class="flex items-start gap-2">
|
|
522
|
+
<input
|
|
523
|
+
v-if="awaiting || challenging"
|
|
524
|
+
type="checkbox"
|
|
525
|
+
class="mt-1 accent-indigo-500"
|
|
526
|
+
data-testid="pr-review-finding-toggle"
|
|
527
|
+
:checked="selected.has(f.id) && !isRetracted(f)"
|
|
528
|
+
:disabled="!awaiting || isRetracted(f)"
|
|
529
|
+
@change="toggle(f.id)"
|
|
530
|
+
/>
|
|
531
|
+
<div class="min-w-0 flex-1">
|
|
532
|
+
<div class="flex flex-wrap items-center gap-1.5">
|
|
533
|
+
<span
|
|
534
|
+
class="rounded px-1.5 py-0.5 text-[10px] font-semibold uppercase ring-1"
|
|
535
|
+
:class="SEVERITY_CLASS[f.severity]"
|
|
536
|
+
>
|
|
537
|
+
{{ t(`prReview.severity.${f.severity}`) }}
|
|
538
|
+
</span>
|
|
539
|
+
<span class="rounded bg-slate-800 px-1.5 py-0.5 text-[10px] text-slate-300">
|
|
540
|
+
{{ t(`prReview.category.${f.category}`) }}
|
|
541
|
+
</span>
|
|
542
|
+
<span
|
|
543
|
+
v-if="postedIds.has(f.id)"
|
|
544
|
+
data-testid="pr-review-finding-posted"
|
|
545
|
+
class="rounded bg-emerald-500/15 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-emerald-300 ring-1 ring-emerald-500/30"
|
|
546
|
+
>
|
|
547
|
+
{{ t('prReview.postReport.postedBadge') }}
|
|
548
|
+
</span>
|
|
549
|
+
<!-- Challenge outcome badges -->
|
|
550
|
+
<span
|
|
551
|
+
v-if="isRetracted(f)"
|
|
552
|
+
data-testid="pr-review-finding-retracted"
|
|
553
|
+
class="rounded bg-rose-500/15 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-rose-300 ring-1 ring-rose-500/30"
|
|
554
|
+
>
|
|
555
|
+
{{ t('prReview.challenge.retractedBadge') }}
|
|
556
|
+
</span>
|
|
557
|
+
<span
|
|
558
|
+
v-else-if="isAmended(f)"
|
|
559
|
+
data-testid="pr-review-finding-amended"
|
|
560
|
+
class="rounded bg-sky-500/15 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-sky-300 ring-1 ring-sky-500/30"
|
|
561
|
+
>
|
|
562
|
+
{{ t('prReview.challenge.strengthenedBadge') }}
|
|
563
|
+
</span>
|
|
564
|
+
<span
|
|
565
|
+
v-else-if="isUpheld(f)"
|
|
566
|
+
data-testid="pr-review-finding-upheld"
|
|
567
|
+
class="rounded bg-emerald-500/15 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-emerald-300 ring-1 ring-emerald-500/30"
|
|
568
|
+
>
|
|
569
|
+
{{ t('prReview.challenge.upheldBadge') }}
|
|
570
|
+
</span>
|
|
571
|
+
<span
|
|
572
|
+
v-else-if="isChallengeFailed(f)"
|
|
573
|
+
data-testid="pr-review-finding-challenge-failed"
|
|
574
|
+
class="rounded bg-amber-500/15 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-amber-300 ring-1 ring-amber-500/30"
|
|
575
|
+
>
|
|
576
|
+
{{ t('prReview.challenge.failedBadge') }}
|
|
577
|
+
</span>
|
|
578
|
+
<span
|
|
579
|
+
v-else-if="isInvestigating(f)"
|
|
580
|
+
data-testid="pr-review-finding-investigating"
|
|
581
|
+
class="flex items-center gap-1 rounded bg-indigo-500/15 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-indigo-300 ring-1 ring-indigo-500/30"
|
|
582
|
+
>
|
|
583
|
+
<UIcon name="i-lucide-loader-circle" class="h-3 w-3 animate-spin" />
|
|
584
|
+
{{ t('prReview.challenge.investigatingBadge') }}
|
|
585
|
+
</span>
|
|
586
|
+
<h4
|
|
587
|
+
class="min-w-0 flex-1 text-[13px] font-medium text-slate-100"
|
|
588
|
+
:class="isRetracted(f) ? 'line-through' : ''"
|
|
589
|
+
>
|
|
590
|
+
{{ f.title }}
|
|
591
|
+
</h4>
|
|
592
|
+
</div>
|
|
593
|
+
<p class="mt-0.5 text-[11px] text-slate-500">
|
|
594
|
+
{{ f.path
|
|
595
|
+
}}<template v-if="f.line != null">
|
|
596
|
+
· {{ t('prReview.line', { line: f.line }) }}</template
|
|
597
|
+
>
|
|
598
|
+
</p>
|
|
599
|
+
<p
|
|
600
|
+
class="mt-1 whitespace-pre-wrap text-[12px] text-slate-300"
|
|
580
601
|
:class="isRetracted(f) ? 'line-through' : ''"
|
|
581
602
|
>
|
|
582
|
-
{{ f.
|
|
583
|
-
</
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}}<template v-if="f.line != null">
|
|
588
|
-
· {{ t('prReview.line', { line: f.line }) }}</template
|
|
603
|
+
{{ f.detail }}
|
|
604
|
+
</p>
|
|
605
|
+
<p
|
|
606
|
+
v-if="f.suggestedFix"
|
|
607
|
+
class="mt-1 whitespace-pre-wrap rounded-md bg-slate-800/50 px-2 py-1 text-[11px] text-slate-300"
|
|
589
608
|
>
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
:class="isRetracted(f) ? 'line-through' : ''"
|
|
594
|
-
>
|
|
595
|
-
{{ f.detail }}
|
|
596
|
-
</p>
|
|
597
|
-
<p
|
|
598
|
-
v-if="f.suggestedFix"
|
|
599
|
-
class="mt-1 whitespace-pre-wrap rounded-md bg-slate-800/50 px-2 py-1 text-[11px] text-slate-300"
|
|
600
|
-
>
|
|
601
|
-
<span class="text-slate-500">{{ t('prReview.suggestedFix') }}</span>
|
|
602
|
-
{{ f.suggestedFix }}
|
|
603
|
-
</p>
|
|
609
|
+
<span class="text-slate-500">{{ t('prReview.suggestedFix') }}</span>
|
|
610
|
+
{{ f.suggestedFix }}
|
|
611
|
+
</p>
|
|
604
612
|
|
|
605
|
-
|
|
613
|
+
<!-- The investigator's justification (why the finding holds up / was retracted),
|
|
606
614
|
or the reason the challenge investigation failed. -->
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
>
|
|
619
|
-
<span class="font-medium">{{
|
|
620
|
-
isChallengeFailed(f)
|
|
621
|
-
? t('prReview.challenge.failedLabel')
|
|
622
|
-
: t('prReview.challenge.verdictLabel')
|
|
623
|
-
}}</span>
|
|
624
|
-
{{ f.challenge.justification }}
|
|
625
|
-
</p>
|
|
626
|
-
|
|
627
|
-
<!-- Per-finding actions: Challenge + Dismiss (only while awaiting a selection). -->
|
|
628
|
-
<div
|
|
629
|
-
v-if="awaiting && !isInvestigating(f)"
|
|
630
|
-
class="mt-1.5 flex items-center gap-3 text-[11px]"
|
|
631
|
-
>
|
|
632
|
-
<button
|
|
633
|
-
v-if="!isRetracted(f)"
|
|
634
|
-
data-testid="pr-review-finding-challenge"
|
|
635
|
-
class="flex items-center gap-1 text-indigo-300 hover:underline disabled:opacity-50"
|
|
636
|
-
:disabled="!canResolve || !access.canExecuteRuns.value"
|
|
637
|
-
@click="openChallenge(f.id)"
|
|
615
|
+
<p
|
|
616
|
+
v-if="f.challenge?.justification"
|
|
617
|
+
data-testid="pr-review-finding-justification"
|
|
618
|
+
class="mt-1.5 whitespace-pre-wrap rounded-md px-2 py-1 text-[11px]"
|
|
619
|
+
:class="
|
|
620
|
+
isRetracted(f)
|
|
621
|
+
? 'bg-rose-500/10 text-rose-200'
|
|
622
|
+
: isChallengeFailed(f)
|
|
623
|
+
? 'bg-amber-500/10 text-amber-200'
|
|
624
|
+
: 'bg-sky-500/10 text-sky-200'
|
|
625
|
+
"
|
|
638
626
|
>
|
|
639
|
-
<
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}}
|
|
645
|
-
</button>
|
|
646
|
-
<button
|
|
647
|
-
data-testid="pr-review-finding-dismiss"
|
|
648
|
-
class="flex items-center gap-1 text-slate-400 hover:text-rose-300 hover:underline disabled:opacity-50"
|
|
649
|
-
:disabled="!canResolve || !access.canExecuteRuns.value"
|
|
650
|
-
@click="onDismiss(f.id)"
|
|
651
|
-
>
|
|
652
|
-
<UIcon name="i-lucide-trash-2" class="h-3.5 w-3.5" />
|
|
653
|
-
{{ t('prReview.challenge.dismiss') }}
|
|
654
|
-
</button>
|
|
655
|
-
</div>
|
|
656
|
-
|
|
657
|
-
<!-- The inline challenge box: an OPTIONAL specific concern for the investigator. -->
|
|
658
|
-
<div
|
|
659
|
-
v-if="challengeForId === f.id"
|
|
660
|
-
data-testid="pr-review-challenge-box"
|
|
661
|
-
class="mt-2 rounded-md border border-indigo-500/40 bg-slate-900/80 p-2"
|
|
662
|
-
>
|
|
663
|
-
<textarea
|
|
664
|
-
v-model="challengeText"
|
|
665
|
-
data-testid="pr-review-challenge-input"
|
|
666
|
-
rows="2"
|
|
667
|
-
:placeholder="t('prReview.challenge.placeholder')"
|
|
668
|
-
class="w-full resize-y rounded border border-slate-700 bg-slate-950/60 px-2 py-1 text-[12px] text-slate-200 outline-none focus:border-indigo-500"
|
|
669
|
-
/>
|
|
670
|
-
<p class="mt-1 text-[10px] text-slate-500">
|
|
671
|
-
{{ t('prReview.challenge.hint') }}
|
|
627
|
+
<span class="font-medium">{{
|
|
628
|
+
isChallengeFailed(f)
|
|
629
|
+
? t('prReview.challenge.failedLabel')
|
|
630
|
+
: t('prReview.challenge.verdictLabel')
|
|
631
|
+
}}</span>
|
|
632
|
+
{{ f.challenge.justification }}
|
|
672
633
|
</p>
|
|
673
|
-
|
|
634
|
+
|
|
635
|
+
<!-- Per-finding actions: Challenge + Dismiss (only while awaiting a selection). -->
|
|
636
|
+
<div
|
|
637
|
+
v-if="awaiting && !isInvestigating(f)"
|
|
638
|
+
class="mt-1.5 flex items-center gap-3 text-[11px]"
|
|
639
|
+
>
|
|
674
640
|
<button
|
|
675
|
-
|
|
676
|
-
|
|
641
|
+
v-if="!isRetracted(f)"
|
|
642
|
+
data-testid="pr-review-finding-challenge"
|
|
643
|
+
class="flex items-center gap-1 text-indigo-300 hover:underline disabled:opacity-50"
|
|
644
|
+
:disabled="!canResolve || !access.canExecuteRuns.value"
|
|
645
|
+
@click="openChallenge(f.id)"
|
|
677
646
|
>
|
|
678
|
-
|
|
647
|
+
<UIcon name="i-lucide-gavel" class="h-3.5 w-3.5" />
|
|
648
|
+
{{
|
|
649
|
+
f.challenge
|
|
650
|
+
? t('prReview.challenge.reChallenge')
|
|
651
|
+
: t('prReview.challenge.action')
|
|
652
|
+
}}
|
|
679
653
|
</button>
|
|
680
654
|
<button
|
|
681
|
-
data-testid="pr-review-
|
|
682
|
-
class="
|
|
683
|
-
:disabled="!canResolve"
|
|
684
|
-
@click="
|
|
655
|
+
data-testid="pr-review-finding-dismiss"
|
|
656
|
+
class="flex items-center gap-1 text-slate-400 hover:text-rose-300 hover:underline disabled:opacity-50"
|
|
657
|
+
:disabled="!canResolve || !access.canExecuteRuns.value"
|
|
658
|
+
@click="onDismiss(f.id)"
|
|
685
659
|
>
|
|
686
|
-
|
|
660
|
+
<UIcon name="i-lucide-trash-2" class="h-3.5 w-3.5" />
|
|
661
|
+
{{ t('prReview.challenge.dismiss') }}
|
|
687
662
|
</button>
|
|
688
663
|
</div>
|
|
664
|
+
|
|
665
|
+
<!-- The inline challenge box: an OPTIONAL specific concern for the investigator. -->
|
|
666
|
+
<div
|
|
667
|
+
v-if="challengeForId === f.id"
|
|
668
|
+
data-testid="pr-review-challenge-box"
|
|
669
|
+
class="mt-2 rounded-md border border-indigo-500/40 bg-slate-900/80 p-2"
|
|
670
|
+
>
|
|
671
|
+
<textarea
|
|
672
|
+
v-model="challengeText"
|
|
673
|
+
data-testid="pr-review-challenge-input"
|
|
674
|
+
rows="2"
|
|
675
|
+
:placeholder="t('prReview.challenge.placeholder')"
|
|
676
|
+
class="w-full resize-y rounded border border-slate-700 bg-slate-950/60 px-2 py-1 text-[12px] text-slate-200 outline-none focus:border-indigo-500"
|
|
677
|
+
/>
|
|
678
|
+
<p class="mt-1 text-[10px] text-slate-500">
|
|
679
|
+
{{ t('prReview.challenge.hint') }}
|
|
680
|
+
</p>
|
|
681
|
+
<div class="mt-1.5 flex justify-end gap-2">
|
|
682
|
+
<button
|
|
683
|
+
class="rounded px-2 py-1 text-[11px] text-slate-400 hover:text-slate-200"
|
|
684
|
+
@click="cancelChallenge"
|
|
685
|
+
>
|
|
686
|
+
{{ t('common.cancel') }}
|
|
687
|
+
</button>
|
|
688
|
+
<button
|
|
689
|
+
data-testid="pr-review-challenge-submit"
|
|
690
|
+
class="rounded bg-indigo-500/80 px-2 py-1 text-[11px] font-medium text-white hover:bg-indigo-500 disabled:opacity-50"
|
|
691
|
+
:disabled="!canResolve"
|
|
692
|
+
@click="submitChallenge(f.id)"
|
|
693
|
+
>
|
|
694
|
+
{{ t('prReview.challenge.submit') }}
|
|
695
|
+
</button>
|
|
696
|
+
</div>
|
|
697
|
+
</div>
|
|
689
698
|
</div>
|
|
690
699
|
</div>
|
|
691
|
-
</
|
|
692
|
-
</
|
|
693
|
-
</
|
|
700
|
+
</article>
|
|
701
|
+
</section>
|
|
702
|
+
</template>
|
|
694
703
|
</template>
|
|
695
|
-
</
|
|
704
|
+
</div>
|
|
705
|
+
|
|
706
|
+
<!-- Run details: the same timing / model / run id + LLM model-activity rollup (calls,
|
|
707
|
+
tokens) every step-backed result window carries, so the reviewer's run reads the
|
|
708
|
+
same "which run is this / how did the model do" facts as the generic step detail. -->
|
|
709
|
+
<aside
|
|
710
|
+
v-if="step"
|
|
711
|
+
data-testid="pr-review-run-meta"
|
|
712
|
+
class="hidden w-60 shrink-0 flex-col gap-4 overflow-y-auto border-s border-slate-800 bg-slate-900/50 px-4 py-4 lg:flex"
|
|
713
|
+
>
|
|
714
|
+
<StepRunMeta
|
|
715
|
+
:step="step"
|
|
716
|
+
:instance-id="instanceId ?? undefined"
|
|
717
|
+
:step-number="stepIndex === null ? undefined : stepIndex + 1"
|
|
718
|
+
:total-steps="instance?.steps.length"
|
|
719
|
+
:run-failed="instance?.status === 'failed'"
|
|
720
|
+
:failure-at="instance?.failure?.occurredAt"
|
|
721
|
+
/>
|
|
722
|
+
</aside>
|
|
696
723
|
</div>
|
|
697
724
|
|
|
698
725
|
<!-- Footer -->
|