@cat-factory/app 0.280.0 → 0.280.2
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/README.md +26 -1
- package/app/components/binaryCandidates/BinaryCandidatesWindow.vue +142 -5
- package/app/components/board/AddTaskModal.vue +9 -0
- package/app/components/board/ReviewFrictionDialog.vue +35 -4
- package/app/components/board/TaskDependencyEdges.vue +26 -15
- package/app/components/board/nodes/TaskCard.vue +6 -1
- package/app/components/brainstorm/BrainstormWindow.vue +19 -1
- package/app/components/common/AsyncViewError.vue +30 -0
- package/app/components/common/ConfirmDialog.vue +26 -0
- package/app/components/docs/DocInterviewWindow.vue +40 -38
- package/app/components/followUp/FollowUpWindow.vue +32 -2
- package/app/components/forkDecision/ForkDecisionWindow.vue +44 -5
- package/app/components/gates/GateResultView.vue +14 -1
- package/app/components/humanTest/HumanTestWindow.vue +13 -1
- package/app/components/initiative/InitiativePlanDecision.vue +16 -3
- package/app/components/initiative/InitiativePlanReview.vue +16 -1
- package/app/components/initiative/InitiativePlanningWindow.vue +50 -52
- package/app/components/initiative/InitiativeTrackerWindow.vue +59 -1
- package/app/components/judge/JudgeResultView.vue +14 -1
- package/app/components/panels/AgentStepDetail.vue +651 -659
- package/app/components/panels/InspectorPanel.vue +6 -0
- package/app/components/panels/ResultWindowDrafts.logic.spec.ts +233 -0
- package/app/components/panels/inspector/ServiceTestSecrets.vue +17 -6
- package/app/components/pipeline/PipelineHealthModal.vue +55 -16
- package/app/components/prReview/PrReviewWindow.vue +23 -4
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +19 -1
- package/app/composables/useBoardActivity.ts +62 -6
- package/app/composables/useConfirm.spec.ts +62 -0
- package/app/composables/useConfirm.ts +6 -1
- package/app/composables/useInterviewDrafts.spec.ts +198 -0
- package/app/composables/useInterviewDrafts.ts +184 -0
- package/app/composables/useTaskExpansion.ts +10 -25
- package/app/docs/consumer-extensions.md +9 -0
- package/app/modular/result-views.ts +58 -21
- package/app/pages/index.vue +68 -64
- package/app/stores/binaryCandidates.ts +26 -3
- package/app/stores/ui/modals.ts +11 -0
- package/app/utils/asyncView.ts +24 -0
- package/app/utils/binaryCandidates.spec.ts +45 -1
- package/app/utils/binaryCandidates.ts +33 -0
- package/app/utils/blockRects.spec.ts +82 -0
- package/app/utils/blockRects.ts +61 -0
- package/app/utils/boardWakeGate.spec.ts +101 -0
- package/app/utils/boardWakeGate.ts +78 -0
- package/i18n/locales/de.json +25 -2
- package/i18n/locales/en.json +25 -2
- package/i18n/locales/es.json +25 -2
- package/i18n/locales/fr.json +25 -2
- package/i18n/locales/he.json +25 -2
- package/i18n/locales/it.json +25 -2
- package/i18n/locales/ja.json +25 -2
- package/i18n/locales/pl.json +25 -2
- package/i18n/locales/tr.json +25 -2
- package/i18n/locales/uk.json +25 -2
- package/package.json +1 -1
|
@@ -356,710 +356,702 @@ async function copyOutput() {
|
|
|
356
356
|
</script>
|
|
357
357
|
|
|
358
358
|
<template>
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
359
|
+
<!-- The Teleport and the reader-fade Transition are owned by the parent mount
|
|
360
|
+
(pages/index.vue), so the fade plays in BOTH directions across the v-if that mounts and
|
|
361
|
+
unmounts this chunk. An inner Transition cannot: it never animates its own first render
|
|
362
|
+
(a Transition without `appear` treats a mount as no transition at all), and it is torn
|
|
363
|
+
down before it could run a leave. Same split, and the same reason, as BlockFocusView. -->
|
|
364
|
+
<!-- `data-agent-kind` names WHICH step this window is scoped to, as the kind rather than as
|
|
365
|
+
the heading beside it: the heading is a translated display label, so it is the wrong
|
|
366
|
+
thing to read for anything that needs to know which agent's step is open. -->
|
|
367
|
+
<div
|
|
368
|
+
v-if="open && step && agent"
|
|
369
|
+
data-testid="step-detail"
|
|
370
|
+
:data-agent-kind="step?.agentKind"
|
|
371
|
+
class="fixed inset-0 z-50 flex max-h-[100dvh] bg-slate-950/96 backdrop-blur-sm"
|
|
372
|
+
role="dialog"
|
|
373
|
+
aria-modal="true"
|
|
374
|
+
>
|
|
375
|
+
<!-- ToC sidebar (only meaningful when there are prose headings) -->
|
|
376
|
+
<aside
|
|
377
|
+
v-if="outline.hasToc"
|
|
378
|
+
data-testid="step-detail-toc"
|
|
379
|
+
class="hidden w-72 shrink-0 flex-col border-e border-slate-800 bg-slate-900/60 md:flex"
|
|
380
|
+
>
|
|
381
|
+
<div class="border-b border-slate-800 px-4 py-3">
|
|
382
|
+
<div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
383
|
+
{{ t('panels.stepDetail.contents') }}
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
<nav class="flex-1 space-y-0.5 overflow-auto px-2 py-3">
|
|
387
|
+
<button
|
|
388
|
+
class="block w-full truncate rounded-md px-2 py-1 text-start text-[13px] transition"
|
|
389
|
+
:class="
|
|
390
|
+
activeId === 'step-details'
|
|
391
|
+
? 'bg-indigo-500/15 font-medium text-indigo-200'
|
|
392
|
+
: 'text-slate-400 hover:bg-slate-800/60 hover:text-slate-200'
|
|
393
|
+
"
|
|
394
|
+
@click="goTo('step-details')"
|
|
377
395
|
>
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
:key="s.id"
|
|
398
|
-
class="block w-full truncate rounded-md px-2 py-1 text-start text-[13px] transition"
|
|
399
|
-
:class="
|
|
400
|
-
activeId === s.id
|
|
401
|
-
? 'bg-indigo-500/15 font-medium text-indigo-200'
|
|
402
|
-
: 'text-slate-400 hover:bg-slate-800/60 hover:text-slate-200'
|
|
403
|
-
"
|
|
404
|
-
:style="{ paddingLeft: `${(s.depth - outline.minDepth) * 0.85 + 0.5}rem` }"
|
|
405
|
-
:title="s.title"
|
|
406
|
-
@click="goTo(s.id)"
|
|
407
|
-
>
|
|
408
|
-
{{ s.title }}
|
|
409
|
-
</button>
|
|
410
|
-
</nav>
|
|
411
|
-
</aside>
|
|
396
|
+
{{ t('panels.stepDetail.details') }}
|
|
397
|
+
</button>
|
|
398
|
+
<button
|
|
399
|
+
v-for="s in tocSections"
|
|
400
|
+
:key="s.id"
|
|
401
|
+
class="block w-full truncate rounded-md px-2 py-1 text-start text-[13px] transition"
|
|
402
|
+
:class="
|
|
403
|
+
activeId === s.id
|
|
404
|
+
? 'bg-indigo-500/15 font-medium text-indigo-200'
|
|
405
|
+
: 'text-slate-400 hover:bg-slate-800/60 hover:text-slate-200'
|
|
406
|
+
"
|
|
407
|
+
:style="{ paddingLeft: `${(s.depth - outline.minDepth) * 0.85 + 0.5}rem` }"
|
|
408
|
+
:title="s.title"
|
|
409
|
+
@click="goTo(s.id)"
|
|
410
|
+
>
|
|
411
|
+
{{ s.title }}
|
|
412
|
+
</button>
|
|
413
|
+
</nav>
|
|
414
|
+
</aside>
|
|
412
415
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
</div>
|
|
487
|
-
</header>
|
|
416
|
+
<!-- main column -->
|
|
417
|
+
<div class="flex min-w-0 flex-1 flex-col">
|
|
418
|
+
<header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
|
|
419
|
+
<div
|
|
420
|
+
class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg"
|
|
421
|
+
:style="{ backgroundColor: agent.color + '22' }"
|
|
422
|
+
>
|
|
423
|
+
<UIcon :name="agent.icon" class="h-5 w-5" :style="{ color: agent.color }" />
|
|
424
|
+
</div>
|
|
425
|
+
<div class="min-w-0">
|
|
426
|
+
<h1 class="truncate text-base font-semibold text-white">{{ agent.label }}</h1>
|
|
427
|
+
<p v-if="block" class="truncate text-xs text-slate-500">{{ block.title }}</p>
|
|
428
|
+
</div>
|
|
429
|
+
<div class="ms-auto flex items-center gap-1.5">
|
|
430
|
+
<UBadge
|
|
431
|
+
v-if="genericApprovalPending"
|
|
432
|
+
color="warning"
|
|
433
|
+
variant="subtle"
|
|
434
|
+
size="sm"
|
|
435
|
+
class="me-1"
|
|
436
|
+
>
|
|
437
|
+
<UIcon name="i-lucide-shield-check" class="me-1 h-3 w-3" />
|
|
438
|
+
{{ t('panels.stepDetail.approvalRequired') }}
|
|
439
|
+
</UBadge>
|
|
440
|
+
<UBadge
|
|
441
|
+
v-else-if="companionExceeded || dedicatedPark"
|
|
442
|
+
color="warning"
|
|
443
|
+
variant="subtle"
|
|
444
|
+
size="sm"
|
|
445
|
+
class="me-1"
|
|
446
|
+
>
|
|
447
|
+
<UIcon name="i-lucide-alert-triangle" class="me-1 h-3 w-3" />
|
|
448
|
+
{{ t('panels.stepDetail.decisionRequired') }}
|
|
449
|
+
</UBadge>
|
|
450
|
+
<UButton
|
|
451
|
+
v-if="outline.sections.length"
|
|
452
|
+
:icon="allCollapsed ? 'i-lucide-unfold-vertical' : 'i-lucide-fold-vertical'"
|
|
453
|
+
color="neutral"
|
|
454
|
+
variant="ghost"
|
|
455
|
+
size="sm"
|
|
456
|
+
:title="
|
|
457
|
+
allCollapsed ? t('panels.stepDetail.expandAll') : t('panels.stepDetail.collapseAll')
|
|
458
|
+
"
|
|
459
|
+
@click="setAll(!allCollapsed)"
|
|
460
|
+
/>
|
|
461
|
+
<UButton
|
|
462
|
+
v-if="hasOutput"
|
|
463
|
+
icon="i-lucide-copy"
|
|
464
|
+
color="neutral"
|
|
465
|
+
variant="ghost"
|
|
466
|
+
size="sm"
|
|
467
|
+
:title="t('panels.stepDetail.copyRawOutput')"
|
|
468
|
+
@click="copyOutput"
|
|
469
|
+
/>
|
|
470
|
+
<!-- Restart the pipeline from this step (shared two-click confirm; resetting
|
|
471
|
+
later steps is destructive). Keyed on the step so its armed state resets
|
|
472
|
+
when a different step opens within this overlay. -->
|
|
473
|
+
<StepRestartControl
|
|
474
|
+
:key="`${ctx?.instanceId}:${ctx?.stepIndex}`"
|
|
475
|
+
:instance-id="ctx?.instanceId ?? null"
|
|
476
|
+
:step-index="ctx?.stepIndex ?? null"
|
|
477
|
+
@restarted="close"
|
|
478
|
+
/>
|
|
479
|
+
<UButton
|
|
480
|
+
icon="i-lucide-x"
|
|
481
|
+
color="neutral"
|
|
482
|
+
variant="ghost"
|
|
483
|
+
size="sm"
|
|
484
|
+
:title="t('panels.stepDetail.closeEsc')"
|
|
485
|
+
@click="close"
|
|
486
|
+
/>
|
|
487
|
+
</div>
|
|
488
|
+
</header>
|
|
488
489
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
490
|
+
<div ref="scrollEl" class="flex-1 overflow-auto px-6 py-6" @scroll="onScroll">
|
|
491
|
+
<div class="mx-auto max-w-3xl space-y-5">
|
|
492
|
+
<!-- metadata card (always shown) -->
|
|
493
|
+
<section
|
|
494
|
+
id="step-details"
|
|
495
|
+
:ref="(el) => (sectionEls['step-details'] = el as HTMLElement | null)"
|
|
496
|
+
class="scroll-mt-4 rounded-xl border border-slate-800 bg-slate-900/50 p-4"
|
|
497
|
+
>
|
|
498
|
+
<StepMetadataCard
|
|
499
|
+
:step="step"
|
|
500
|
+
:run-failed="runFailed"
|
|
501
|
+
:run-active="runActive"
|
|
502
|
+
:duration-label="durationLabel"
|
|
503
|
+
:is-running="isRunning"
|
|
504
|
+
:step-number="stepNumber"
|
|
505
|
+
:total-steps="totalSteps"
|
|
506
|
+
:instance-id="instance?.id"
|
|
507
|
+
:companion-verdicts="companionVerdicts"
|
|
508
|
+
:latest-verdict="latestVerdict"
|
|
509
|
+
/>
|
|
510
|
+
</section>
|
|
510
511
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
512
|
+
<!-- post-run Kaizen grading status + results for this step (run-details only) -->
|
|
513
|
+
<KaizenStepStatus
|
|
514
|
+
:instance-id="ctx?.instanceId ?? null"
|
|
515
|
+
:step-index="ctx?.stepIndex ?? null"
|
|
516
|
+
/>
|
|
516
517
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
518
|
+
<!-- companion rework budget spent, OR the loop abandoned early as unproductive,
|
|
519
|
+
with or without must-fix findings still open: the shared iteration-cap decision
|
|
520
|
+
(one more round / proceed with the current output / stop & reset). One prompt,
|
|
521
|
+
and the two slots are picked on different facts — the choices are the same but
|
|
522
|
+
the reason is not, the spent-limit wording is untrue of a stalled loop, and an
|
|
523
|
+
open blocker is what the person is actually being asked to overrule. -->
|
|
524
|
+
<IterationCapPrompt
|
|
525
|
+
v-if="companionExceeded"
|
|
526
|
+
:heading="
|
|
527
|
+
companionStalled
|
|
528
|
+
? t('panels.stepDetail.companionStalledHeading', {
|
|
529
|
+
agent: agent.label,
|
|
530
|
+
attempts: step.companion?.attempts,
|
|
531
|
+
maxAttempts: step.companion?.maxAttempts,
|
|
532
|
+
})
|
|
533
|
+
: blockingFindings.length
|
|
534
|
+
? t(
|
|
535
|
+
'panels.stepDetail.companionCapBlockedHeading',
|
|
536
|
+
{
|
|
528
537
|
agent: agent.label,
|
|
529
|
-
attempts: step.companion?.
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
? t('panels.stepDetail.companionCapBlockedDetail')
|
|
551
|
-
: companionStalled
|
|
552
|
-
? t('panels.stepDetail.companionStalledDetail')
|
|
553
|
-
: t('panels.stepDetail.companionCapDetail')
|
|
554
|
-
"
|
|
555
|
-
:loading="resolvingCap"
|
|
556
|
-
@resolve="resolveCompanionCap"
|
|
557
|
-
/>
|
|
538
|
+
attempts: step.companion?.maxAttempts,
|
|
539
|
+
count: blockingFindings.length,
|
|
540
|
+
},
|
|
541
|
+
blockingFindings.length,
|
|
542
|
+
)
|
|
543
|
+
: t('panels.stepDetail.companionCapHeading', {
|
|
544
|
+
agent: agent.label,
|
|
545
|
+
attempts: step.companion?.maxAttempts,
|
|
546
|
+
threshold: pctOf(latestVerdict?.threshold ?? 0),
|
|
547
|
+
})
|
|
548
|
+
"
|
|
549
|
+
:detail="
|
|
550
|
+
blockingFindings.length
|
|
551
|
+
? t('panels.stepDetail.companionCapBlockedDetail')
|
|
552
|
+
: companionStalled
|
|
553
|
+
? t('panels.stepDetail.companionStalledDetail')
|
|
554
|
+
: t('panels.stepDetail.companionCapDetail')
|
|
555
|
+
"
|
|
556
|
+
:loading="resolvingCap"
|
|
557
|
+
@resolve="resolveCompanionCap"
|
|
558
|
+
/>
|
|
558
559
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
560
|
+
<!-- a park a dedicated window owns (fork choice / follow-up triage / candidate
|
|
561
|
+
comparison): the generic approval rail can't resolve it (the server refuses),
|
|
562
|
+
so point the human at the window that can. Copy and icon come from the shared
|
|
563
|
+
per-park table, so a park added to the vocabulary can never inherit another
|
|
564
|
+
one's wording here. -->
|
|
565
|
+
<!-- the pre-dispatch input gate holds this step: answered here, in place -->
|
|
566
|
+
<InputGateNotice
|
|
567
|
+
v-if="inputGateVerdict && instance"
|
|
568
|
+
:gate="inputGateVerdict"
|
|
569
|
+
tone="blocked"
|
|
570
|
+
:execution-id="instance.id"
|
|
571
|
+
compact
|
|
572
|
+
/>
|
|
572
573
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
574
|
+
<div
|
|
575
|
+
v-if="parkPresentation"
|
|
576
|
+
class="rounded-xl border border-amber-500/30 bg-amber-500/5 p-4"
|
|
577
|
+
data-testid="dedicated-park-redirect"
|
|
578
|
+
:data-park="dedicatedPark"
|
|
579
|
+
>
|
|
580
|
+
<p class="text-[13px] leading-relaxed text-amber-200/90">
|
|
581
|
+
{{ t(parkPresentation.noticeKey) }}
|
|
582
|
+
</p>
|
|
583
|
+
<UButton
|
|
584
|
+
class="mt-3"
|
|
585
|
+
color="primary"
|
|
586
|
+
size="sm"
|
|
587
|
+
:icon="parkPresentation.icon"
|
|
588
|
+
data-testid="dedicated-park-open"
|
|
589
|
+
@click="openDedicatedWindow"
|
|
590
|
+
>
|
|
591
|
+
{{ t(parkPresentation.actionKey) }}
|
|
592
|
+
</UButton>
|
|
593
|
+
</div>
|
|
593
594
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
595
|
+
<!-- ephemeral environment lifecycle (spinning up / running / shut down /
|
|
596
|
+
errored + the exact error), when this step runs against one -->
|
|
597
|
+
<EnvironmentStatusPanel
|
|
598
|
+
v-if="stepEnvironment"
|
|
599
|
+
:environment="stepEnvironment"
|
|
600
|
+
:run-active="runActive"
|
|
601
|
+
/>
|
|
601
602
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
603
|
+
<!-- frontend UI-test: how the frame's backend bindings resolved (env var →
|
|
604
|
+
live URL | mocked) + the run-start advisories (duplicate env vars /
|
|
605
|
+
partially-mocked services) the engine stamped on the run. Rendered from the
|
|
606
|
+
FROZEN start-time bindings so a finished run shows what it actually drove
|
|
607
|
+
against, not a live re-resolution. -->
|
|
608
|
+
<FrontendBindingsResolved
|
|
609
|
+
v-if="frontendConfig"
|
|
610
|
+
:config="frontendConfig"
|
|
611
|
+
:resolved="frontendBindings"
|
|
612
|
+
/>
|
|
613
|
+
<ul v-if="runNotes.length" class="space-y-1" data-testid="run-notes">
|
|
614
|
+
<li
|
|
615
|
+
v-for="(note, i) in runNotes"
|
|
616
|
+
:key="i"
|
|
617
|
+
class="flex items-start gap-1.5 text-[11px] leading-snug text-amber-300/80"
|
|
618
|
+
>
|
|
619
|
+
<UIcon name="i-lucide-info" class="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
|
620
|
+
<span>{{ note }}</span>
|
|
621
|
+
</li>
|
|
622
|
+
</ul>
|
|
622
623
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
624
|
+
<!-- this run's infrastructure attempts (container/runner/env spin-up +
|
|
625
|
+
tear-down): the surface for the per-run container log rows + the exact
|
|
626
|
+
provider error, behind a toggle (most useful on a failed-to-start run) -->
|
|
627
|
+
<div v-if="executionId">
|
|
628
|
+
<UButton
|
|
629
|
+
:icon="showProvisioning ? 'i-lucide-chevron-up' : 'i-lucide-scroll-text'"
|
|
630
|
+
variant="ghost"
|
|
631
|
+
size="xs"
|
|
632
|
+
@click="
|
|
633
|
+
() => {
|
|
634
|
+
showProvisioning = !showProvisioning
|
|
635
|
+
}
|
|
636
|
+
"
|
|
637
|
+
>
|
|
638
|
+
{{
|
|
639
|
+
showProvisioning
|
|
640
|
+
? t('panels.stepDetail.hideInfraAttempts')
|
|
641
|
+
: t('panels.stepDetail.infraAttempts')
|
|
642
|
+
}}
|
|
643
|
+
</UButton>
|
|
644
|
+
<ProvisioningLogsDrawer
|
|
645
|
+
v-if="showProvisioning"
|
|
646
|
+
class="mt-2"
|
|
647
|
+
:execution-id="executionId"
|
|
648
|
+
:live="runActive"
|
|
649
|
+
/>
|
|
650
|
+
</div>
|
|
650
651
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
652
|
+
<!-- this step's execution history (the run-level trail narrowed to this step),
|
|
653
|
+
behind a toggle — a merged timeline of the SUCCESSFUL outputs a restart
|
|
654
|
+
superseded and the FAILED attempts, scoped to the step being looked at -->
|
|
655
|
+
<div v-if="hasStepHistory">
|
|
656
|
+
<UButton
|
|
657
|
+
:icon="showHistory ? 'i-lucide-chevron-up' : 'i-lucide-history'"
|
|
658
|
+
variant="ghost"
|
|
659
|
+
size="xs"
|
|
660
|
+
data-testid="step-execution-history-toggle"
|
|
661
|
+
@click="
|
|
662
|
+
() => {
|
|
663
|
+
showHistory = !showHistory
|
|
664
|
+
}
|
|
665
|
+
"
|
|
666
|
+
>
|
|
667
|
+
{{
|
|
668
|
+
showHistory
|
|
669
|
+
? t('panels.stepDetail.hideExecutionHistory')
|
|
670
|
+
: t('panels.stepDetail.executionHistory')
|
|
671
|
+
}}
|
|
672
|
+
</UButton>
|
|
673
|
+
<StepExecutionHistory
|
|
674
|
+
v-if="showHistory"
|
|
675
|
+
class="mt-2"
|
|
676
|
+
:failures="stepFailures"
|
|
677
|
+
:outputs="stepOutputs"
|
|
678
|
+
data-testid="step-execution-history"
|
|
679
|
+
/>
|
|
680
|
+
</div>
|
|
680
681
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
682
|
+
<!-- tester report: what was tested, the per-area outcomes, the concerns
|
|
683
|
+
it raised and the greenlight verdict; plus the fixer-loop phase -->
|
|
684
|
+
<StepTestReport v-if="testReport" :report="testReport" :phase="testPhase" />
|
|
684
685
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
686
|
+
<!-- code/PR reviewer's best-practice adherence: per standard, a 1..10 rating of how
|
|
687
|
+
well the change adheres + the issues it surfaced. Only on a review step. -->
|
|
688
|
+
<StepFragmentAdherence
|
|
689
|
+
v-if="step.fragmentAdherence?.length"
|
|
690
|
+
:items="step.fragmentAdherence"
|
|
691
|
+
/>
|
|
691
692
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
693
|
+
<!-- container agent's effort self-assessment (how hard it was, what reduced its
|
|
694
|
+
effectiveness, key obstacles). Only when the agent reported one. -->
|
|
695
|
+
<StepEffortReport v-if="step.effortReport" :report="step.effortReport" />
|
|
695
696
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
697
|
+
<!-- the tool servers (MCP) this dispatch wired, and the ones it dropped with the
|
|
698
|
+
reason. Only on a container step, and self-hiding when the record holds
|
|
699
|
+
nothing: a recorded pair of empty lists is a kind that declared none, which is
|
|
700
|
+
every step on a deployment that registers no tool servers at all. -->
|
|
701
|
+
<StepToolServers
|
|
702
|
+
v-if="step.toolServers"
|
|
703
|
+
:tool-servers="step.toolServers"
|
|
704
|
+
:step-agent-kind="step.agentKind"
|
|
705
|
+
/>
|
|
705
706
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
707
|
+
<!-- the bugfix REPRODUCTION PROOF: the declared reproducing check run against the
|
|
708
|
+
pre-fix tree and the final one, with both captured outputs, or the agent's
|
|
709
|
+
structural declaration that the bug cannot be reproduced. This panel is the
|
|
710
|
+
half that matters most: the engine records the proof on whichever step OPENED
|
|
711
|
+
the pull request, and in every built-in pipeline that is the `coder` — a kind
|
|
712
|
+
with no dedicated result view, so it opens HERE and the result-window shell is
|
|
713
|
+
never involved. Self-hiding for a run that declared no reproducing check. -->
|
|
714
|
+
<StepReproductionReport
|
|
715
|
+
v-if="step.reproduction"
|
|
716
|
+
:report="step.reproduction"
|
|
717
|
+
variant="card"
|
|
718
|
+
/>
|
|
718
719
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
720
|
+
<!-- what the step declared it stored through a foundational storage service, and
|
|
721
|
+
every way that record is incomplete. This panel is the OTHER half of the
|
|
722
|
+
result-window shell's trailing section: a step whose kind declares no
|
|
723
|
+
dedicated result view opens here instead, and the shell is not involved — so
|
|
724
|
+
without this the artifacts of exactly the kinds that need no bespoke window
|
|
725
|
+
would have nowhere to appear. Self-hiding when the step has neither a report
|
|
726
|
+
nor a selection, which is every step of every stock pipeline. -->
|
|
727
|
+
<BinaryOutputReport :step="step" variant="card" />
|
|
727
728
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
729
|
+
<!-- edit-then-approve: a direct editor over the raw conclusions; the
|
|
730
|
+
edits become the approved proposal that flows to the next step -->
|
|
731
|
+
<section v-if="editing" class="scroll-mt-4">
|
|
732
|
+
<div class="mb-2 flex items-center gap-1.5 text-[11px] text-amber-400">
|
|
733
|
+
<UIcon name="i-lucide-pencil" class="h-3.5 w-3.5" />
|
|
734
|
+
<span class="font-semibold uppercase tracking-wide">{{
|
|
735
|
+
t('panels.stepDetail.editingConclusions')
|
|
736
|
+
}}</span>
|
|
737
|
+
</div>
|
|
738
|
+
<UTextarea
|
|
739
|
+
v-model="draftProposal"
|
|
740
|
+
:rows="22"
|
|
741
|
+
autoresize
|
|
742
|
+
size="sm"
|
|
743
|
+
class="w-full"
|
|
744
|
+
:ui="{ base: 'font-mono text-[12px] leading-relaxed' }"
|
|
745
|
+
:placeholder="t('panels.stepDetail.editConclusionsPlaceholder')"
|
|
746
|
+
/>
|
|
747
|
+
</section>
|
|
748
|
+
|
|
749
|
+
<!-- the agent's prose output, sectioned + collapsible -->
|
|
750
|
+
<template v-else-if="hasOutput">
|
|
751
|
+
<section
|
|
752
|
+
v-for="s in outline.sections"
|
|
753
|
+
:id="s.id"
|
|
754
|
+
:key="s.id"
|
|
755
|
+
:ref="(el) => (sectionEls[s.id] = el as HTMLElement | null)"
|
|
756
|
+
class="scroll-mt-4"
|
|
757
|
+
>
|
|
758
|
+
<button
|
|
759
|
+
v-if="s.depth > 0"
|
|
760
|
+
class="group flex w-full items-center gap-2 rounded-md py-1 text-start transition hover:text-white"
|
|
761
|
+
@click="toggle(s.id)"
|
|
762
|
+
>
|
|
763
|
+
<UIcon
|
|
764
|
+
name="i-lucide-chevron-right"
|
|
765
|
+
class="h-4 w-4 shrink-0 text-slate-500 transition-transform group-hover:text-slate-300"
|
|
766
|
+
:class="collapsed[s.id] ? '' : 'rotate-90'"
|
|
767
|
+
/>
|
|
768
|
+
<span
|
|
769
|
+
class="font-semibold text-slate-100"
|
|
770
|
+
:class="s.depth <= 1 ? 'text-lg' : s.depth === 2 ? 'text-base' : 'text-sm'"
|
|
771
|
+
v-html="s.titleHtml"
|
|
745
772
|
/>
|
|
746
|
-
</
|
|
773
|
+
</button>
|
|
774
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
775
|
+
<div
|
|
776
|
+
v-show="!collapsed[s.id]"
|
|
777
|
+
class="reader-prose mt-1 text-[13px] leading-relaxed text-slate-300"
|
|
778
|
+
:class="[
|
|
779
|
+
s.depth > 0 ? 'ps-6' : '',
|
|
780
|
+
genericApprovalPending && !editing ? 'review-mode' : '',
|
|
781
|
+
]"
|
|
782
|
+
@click="onProseClick"
|
|
783
|
+
v-html="s.bodyHtml"
|
|
784
|
+
/>
|
|
785
|
+
</section>
|
|
786
|
+
</template>
|
|
747
787
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
<button
|
|
758
|
-
v-if="s.depth > 0"
|
|
759
|
-
class="group flex w-full items-center gap-2 rounded-md py-1 text-start transition hover:text-white"
|
|
760
|
-
@click="toggle(s.id)"
|
|
761
|
-
>
|
|
762
|
-
<UIcon
|
|
763
|
-
name="i-lucide-chevron-right"
|
|
764
|
-
class="h-4 w-4 shrink-0 text-slate-500 transition-transform group-hover:text-slate-300"
|
|
765
|
-
:class="collapsed[s.id] ? '' : 'rotate-90'"
|
|
766
|
-
/>
|
|
767
|
-
<span
|
|
768
|
-
class="font-semibold text-slate-100"
|
|
769
|
-
:class="s.depth <= 1 ? 'text-lg' : s.depth === 2 ? 'text-base' : 'text-sm'"
|
|
770
|
-
v-html="s.titleHtml"
|
|
771
|
-
/>
|
|
772
|
-
</button>
|
|
773
|
-
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
774
|
-
<div
|
|
775
|
-
v-show="!collapsed[s.id]"
|
|
776
|
-
class="reader-prose mt-1 text-[13px] leading-relaxed text-slate-300"
|
|
777
|
-
:class="[
|
|
778
|
-
s.depth > 0 ? 'ps-6' : '',
|
|
779
|
-
genericApprovalPending && !editing ? 'review-mode' : '',
|
|
780
|
-
]"
|
|
781
|
-
@click="onProseClick"
|
|
782
|
-
v-html="s.bodyHtml"
|
|
783
|
-
/>
|
|
784
|
-
</section>
|
|
785
|
-
</template>
|
|
788
|
+
<p
|
|
789
|
+
v-else
|
|
790
|
+
class="rounded-lg border border-dashed border-slate-800 py-6 text-center text-sm text-slate-500"
|
|
791
|
+
>
|
|
792
|
+
{{ t('panels.stepDetail.noProseOutput') }}
|
|
793
|
+
</p>
|
|
794
|
+
</div>
|
|
795
|
+
</div>
|
|
796
|
+
</div>
|
|
786
797
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
798
|
+
<!-- review rail (approval mode): per-block comments + overall feedback +
|
|
799
|
+
Approve / Request changes / Reject. A end-side rail on wide screens; a
|
|
800
|
+
bottom sheet (still reachable) below lg, so the gate is always actionable. -->
|
|
801
|
+
<aside
|
|
802
|
+
v-if="genericApprovalPending"
|
|
803
|
+
class="absolute inset-x-0 bottom-0 z-10 flex max-h-[70dvh] flex-col rounded-t-2xl border-t border-slate-700 bg-slate-900/95 shadow-2xl backdrop-blur lg:static lg:inset-auto lg:z-auto lg:max-h-none lg:w-96 lg:shrink-0 lg:rounded-none lg:border-s lg:border-t-0 lg:border-slate-800 lg:bg-slate-900/60 lg:shadow-none lg:backdrop-blur-none"
|
|
804
|
+
>
|
|
805
|
+
<div class="border-b border-slate-800 px-4 py-3">
|
|
806
|
+
<div class="text-[11px] font-semibold uppercase tracking-wide text-amber-400">
|
|
807
|
+
{{
|
|
808
|
+
editing
|
|
809
|
+
? t('panels.stepDetail.approveWithCorrections')
|
|
810
|
+
: t('panels.stepDetail.reviewAndApprove')
|
|
811
|
+
}}
|
|
795
812
|
</div>
|
|
813
|
+
<p class="mt-1 text-[12px] text-slate-400">
|
|
814
|
+
{{ editing ? t('panels.stepDetail.editHint') : t('panels.stepDetail.reviewHint') }}
|
|
815
|
+
</p>
|
|
816
|
+
<!-- The gate's configured POLICY, when it has one. Both lines exist because an
|
|
817
|
+
approve on such a gate legitimately may not advance the run: without the tally, a
|
|
818
|
+
correctly-recorded approval is indistinguishable from a call that failed, and
|
|
819
|
+
without the refusal a person would press a button the server answers 403. -->
|
|
820
|
+
<p v-if="gateQuorum" class="mt-1 text-[12px] text-amber-300/90" data-testid="gate-quorum">
|
|
821
|
+
{{
|
|
822
|
+
t('panels.stepDetail.quorumProgress', {
|
|
823
|
+
recorded: gateQuorum.recorded,
|
|
824
|
+
required: gateQuorum.required,
|
|
825
|
+
})
|
|
826
|
+
}}
|
|
827
|
+
<span v-if="viewerHasApproved">{{ t('panels.stepDetail.quorumYours') }}</span>
|
|
828
|
+
</p>
|
|
829
|
+
<p
|
|
830
|
+
v-if="gateRefusal"
|
|
831
|
+
class="mt-1 text-[12px] text-slate-400"
|
|
832
|
+
data-testid="gate-not-approver"
|
|
833
|
+
>
|
|
834
|
+
{{ t(GATE_REFUSAL_KEYS[gateRefusal]) }}
|
|
835
|
+
</p>
|
|
836
|
+
</div>
|
|
796
837
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
v-if="genericApprovalPending"
|
|
802
|
-
class="absolute inset-x-0 bottom-0 z-10 flex max-h-[70dvh] flex-col rounded-t-2xl border-t border-slate-700 bg-slate-900/95 shadow-2xl backdrop-blur lg:static lg:inset-auto lg:z-auto lg:max-h-none lg:w-96 lg:shrink-0 lg:rounded-none lg:border-s lg:border-t-0 lg:border-slate-800 lg:bg-slate-900/60 lg:shadow-none lg:backdrop-blur-none"
|
|
838
|
+
<div class="flex-1 space-y-3 overflow-auto overscroll-contain px-4 py-3">
|
|
839
|
+
<p
|
|
840
|
+
v-if="editing"
|
|
841
|
+
class="rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 text-[12px] leading-relaxed text-amber-200/90"
|
|
803
842
|
>
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
843
|
+
{{ t('panels.stepDetail.editingNotice') }}
|
|
844
|
+
</p>
|
|
845
|
+
<template v-else>
|
|
846
|
+
<!-- composer for the block the human just clicked -->
|
|
847
|
+
<div
|
|
848
|
+
v-if="draftTarget"
|
|
849
|
+
data-testid="step-review-composer"
|
|
850
|
+
class="rounded-lg border border-indigo-500/40 bg-indigo-500/5 p-3"
|
|
851
|
+
>
|
|
852
|
+
<div class="mb-1 text-[10px] uppercase tracking-wide text-indigo-300">
|
|
853
|
+
{{ t('panels.stepDetail.commentingOn') }}
|
|
854
|
+
</div>
|
|
855
|
+
<pre
|
|
856
|
+
class="mb-2 max-h-24 overflow-auto whitespace-pre-wrap rounded bg-slate-950/60 p-2 text-[11px] text-slate-300"
|
|
857
|
+
>{{ draftTarget.quotedSource }}</pre>
|
|
858
|
+
<UTextarea
|
|
859
|
+
v-model="draftBody"
|
|
860
|
+
data-testid="step-review-comment-body"
|
|
861
|
+
:rows="3"
|
|
862
|
+
autoresize
|
|
863
|
+
size="sm"
|
|
864
|
+
class="w-full"
|
|
865
|
+
:placeholder="t('panels.stepDetail.commentPlaceholder')"
|
|
866
|
+
/>
|
|
867
|
+
<div class="mt-2 flex justify-end gap-2">
|
|
868
|
+
<UButton color="neutral" variant="ghost" size="xs" @click="cancelDraft">
|
|
869
|
+
{{ t('common.cancel') }}
|
|
870
|
+
</UButton>
|
|
871
|
+
<UButton
|
|
872
|
+
color="primary"
|
|
873
|
+
size="xs"
|
|
874
|
+
data-testid="step-review-comment-add"
|
|
875
|
+
:disabled="!draftBody.trim()"
|
|
876
|
+
@click="addDraftComment"
|
|
877
|
+
>
|
|
878
|
+
{{ t('panels.stepDetail.addComment') }}
|
|
879
|
+
</UButton>
|
|
811
880
|
</div>
|
|
812
|
-
<p class="mt-1 text-[12px] text-slate-400">
|
|
813
|
-
{{ editing ? t('panels.stepDetail.editHint') : t('panels.stepDetail.reviewHint') }}
|
|
814
|
-
</p>
|
|
815
|
-
<!-- The gate's configured POLICY, when it has one. Both lines exist because an
|
|
816
|
-
approve on such a gate legitimately may not advance the run: without the tally, a
|
|
817
|
-
correctly-recorded approval is indistinguishable from a call that failed, and
|
|
818
|
-
without the refusal a person would press a button the server answers 403. -->
|
|
819
|
-
<p
|
|
820
|
-
v-if="gateQuorum"
|
|
821
|
-
class="mt-1 text-[12px] text-amber-300/90"
|
|
822
|
-
data-testid="gate-quorum"
|
|
823
|
-
>
|
|
824
|
-
{{
|
|
825
|
-
t('panels.stepDetail.quorumProgress', {
|
|
826
|
-
recorded: gateQuorum.recorded,
|
|
827
|
-
required: gateQuorum.required,
|
|
828
|
-
})
|
|
829
|
-
}}
|
|
830
|
-
<span v-if="viewerHasApproved">{{ t('panels.stepDetail.quorumYours') }}</span>
|
|
831
|
-
</p>
|
|
832
|
-
<p
|
|
833
|
-
v-if="gateRefusal"
|
|
834
|
-
class="mt-1 text-[12px] text-slate-400"
|
|
835
|
-
data-testid="gate-not-approver"
|
|
836
|
-
>
|
|
837
|
-
{{ t(GATE_REFUSAL_KEYS[gateRefusal]) }}
|
|
838
|
-
</p>
|
|
839
881
|
</div>
|
|
840
882
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
<
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
v-if="draftTarget"
|
|
852
|
-
data-testid="step-review-composer"
|
|
853
|
-
class="rounded-lg border border-indigo-500/40 bg-indigo-500/5 p-3"
|
|
854
|
-
>
|
|
855
|
-
<div class="mb-1 text-[10px] uppercase tracking-wide text-indigo-300">
|
|
856
|
-
{{ t('panels.stepDetail.commentingOn') }}
|
|
857
|
-
</div>
|
|
858
|
-
<pre
|
|
859
|
-
class="mb-2 max-h-24 overflow-auto whitespace-pre-wrap rounded bg-slate-950/60 p-2 text-[11px] text-slate-300"
|
|
860
|
-
>{{ draftTarget.quotedSource }}</pre>
|
|
861
|
-
<UTextarea
|
|
862
|
-
v-model="draftBody"
|
|
863
|
-
data-testid="step-review-comment-body"
|
|
864
|
-
:rows="3"
|
|
865
|
-
autoresize
|
|
866
|
-
size="sm"
|
|
867
|
-
class="w-full"
|
|
868
|
-
:placeholder="t('panels.stepDetail.commentPlaceholder')"
|
|
869
|
-
/>
|
|
870
|
-
<div class="mt-2 flex justify-end gap-2">
|
|
871
|
-
<UButton color="neutral" variant="ghost" size="xs" @click="cancelDraft">
|
|
872
|
-
{{ t('common.cancel') }}
|
|
873
|
-
</UButton>
|
|
874
|
-
<UButton
|
|
875
|
-
color="primary"
|
|
876
|
-
size="xs"
|
|
877
|
-
data-testid="step-review-comment-add"
|
|
878
|
-
:disabled="!draftBody.trim()"
|
|
879
|
-
@click="addDraftComment"
|
|
880
|
-
>
|
|
881
|
-
{{ t('panels.stepDetail.addComment') }}
|
|
882
|
-
</UButton>
|
|
883
|
-
</div>
|
|
883
|
+
<!-- comments added so far -->
|
|
884
|
+
<div
|
|
885
|
+
v-for="(c, idx) in reviewComments"
|
|
886
|
+
:key="idx"
|
|
887
|
+
data-testid="step-review-comment"
|
|
888
|
+
class="rounded-lg border border-slate-800 bg-slate-900/50 p-3"
|
|
889
|
+
>
|
|
890
|
+
<div class="mb-1 flex items-start justify-between gap-2">
|
|
891
|
+
<div class="text-[10px] uppercase tracking-wide text-slate-500">
|
|
892
|
+
{{ t('panels.stepDetail.commentN', { number: idx + 1 }) }}
|
|
884
893
|
</div>
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
:key="idx"
|
|
890
|
-
data-testid="step-review-comment"
|
|
891
|
-
class="rounded-lg border border-slate-800 bg-slate-900/50 p-3"
|
|
894
|
+
<button
|
|
895
|
+
class="text-slate-500 transition hover:text-rose-400"
|
|
896
|
+
:title="t('panels.stepDetail.removeComment')"
|
|
897
|
+
@click="removeComment(idx)"
|
|
892
898
|
>
|
|
893
|
-
<
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
@click="removeComment(idx)"
|
|
901
|
-
>
|
|
902
|
-
<UIcon name="i-lucide-x" class="h-3.5 w-3.5" />
|
|
903
|
-
</button>
|
|
904
|
-
</div>
|
|
905
|
-
<pre
|
|
906
|
-
class="mb-1 max-h-20 overflow-auto whitespace-pre-wrap rounded bg-slate-950/50 p-1.5 text-[10px] text-slate-400"
|
|
907
|
-
>{{ c.quotedSource }}</pre>
|
|
908
|
-
<p class="text-[12px] text-slate-200">{{ c.body }}</p>
|
|
909
|
-
</div>
|
|
910
|
-
|
|
911
|
-
<div>
|
|
912
|
-
<label
|
|
913
|
-
class="mb-1 block text-[11px] font-semibold uppercase tracking-wide text-slate-400"
|
|
914
|
-
>
|
|
915
|
-
{{ t('panels.stepDetail.overallFeedback') }}
|
|
916
|
-
</label>
|
|
917
|
-
<UTextarea
|
|
918
|
-
v-model="feedback"
|
|
919
|
-
data-testid="step-review-feedback"
|
|
920
|
-
:rows="3"
|
|
921
|
-
autoresize
|
|
922
|
-
size="sm"
|
|
923
|
-
class="w-full"
|
|
924
|
-
:placeholder="t('panels.stepDetail.overallFeedbackPlaceholder')"
|
|
925
|
-
/>
|
|
926
|
-
</div>
|
|
927
|
-
</template>
|
|
899
|
+
<UIcon name="i-lucide-x" class="h-3.5 w-3.5" />
|
|
900
|
+
</button>
|
|
901
|
+
</div>
|
|
902
|
+
<pre
|
|
903
|
+
class="mb-1 max-h-20 overflow-auto whitespace-pre-wrap rounded bg-slate-950/50 p-1.5 text-[10px] text-slate-400"
|
|
904
|
+
>{{ c.quotedSource }}</pre>
|
|
905
|
+
<p class="text-[12px] text-slate-200">{{ c.body }}</p>
|
|
928
906
|
</div>
|
|
929
907
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
class="space-y-2 border-t border-slate-800 px-4 pt-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))]"
|
|
934
|
-
>
|
|
935
|
-
<UButton
|
|
936
|
-
color="primary"
|
|
937
|
-
size="sm"
|
|
938
|
-
icon="i-lucide-check"
|
|
939
|
-
block
|
|
940
|
-
:loading="submitting"
|
|
941
|
-
@click="approveWithEdits"
|
|
908
|
+
<div>
|
|
909
|
+
<label
|
|
910
|
+
class="mb-1 block text-[11px] font-semibold uppercase tracking-wide text-slate-400"
|
|
942
911
|
>
|
|
943
|
-
{{ t('panels.stepDetail.
|
|
944
|
-
</
|
|
912
|
+
{{ t('panels.stepDetail.overallFeedback') }}
|
|
913
|
+
</label>
|
|
914
|
+
<UTextarea
|
|
915
|
+
v-model="feedback"
|
|
916
|
+
data-testid="step-review-feedback"
|
|
917
|
+
:rows="3"
|
|
918
|
+
autoresize
|
|
919
|
+
size="sm"
|
|
920
|
+
class="w-full"
|
|
921
|
+
:placeholder="t('panels.stepDetail.overallFeedbackPlaceholder')"
|
|
922
|
+
/>
|
|
923
|
+
</div>
|
|
924
|
+
</template>
|
|
925
|
+
</div>
|
|
926
|
+
|
|
927
|
+
<!-- edit-then-approve actions -->
|
|
928
|
+
<div
|
|
929
|
+
v-if="editing"
|
|
930
|
+
class="space-y-2 border-t border-slate-800 px-4 pt-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))]"
|
|
931
|
+
>
|
|
932
|
+
<UButton
|
|
933
|
+
color="primary"
|
|
934
|
+
size="sm"
|
|
935
|
+
icon="i-lucide-check"
|
|
936
|
+
block
|
|
937
|
+
:loading="submitting"
|
|
938
|
+
@click="approveWithEdits"
|
|
939
|
+
>
|
|
940
|
+
{{ t('panels.stepDetail.approveWithEdits') }}
|
|
941
|
+
</UButton>
|
|
942
|
+
<UButton
|
|
943
|
+
color="neutral"
|
|
944
|
+
variant="ghost"
|
|
945
|
+
size="sm"
|
|
946
|
+
block
|
|
947
|
+
:disabled="submitting"
|
|
948
|
+
@click="cancelEditing"
|
|
949
|
+
>
|
|
950
|
+
{{ t('panels.stepDetail.cancelEdits') }}
|
|
951
|
+
</UButton>
|
|
952
|
+
</div>
|
|
953
|
+
|
|
954
|
+
<div
|
|
955
|
+
v-else
|
|
956
|
+
class="space-y-2 border-t border-slate-800 px-4 pt-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))]"
|
|
957
|
+
>
|
|
958
|
+
<UButton
|
|
959
|
+
color="primary"
|
|
960
|
+
data-testid="step-approve"
|
|
961
|
+
size="sm"
|
|
962
|
+
icon="i-lucide-check"
|
|
963
|
+
block
|
|
964
|
+
:disabled="rejectArmed || !!gateRefusal"
|
|
965
|
+
:loading="submitting"
|
|
966
|
+
@click="approve"
|
|
967
|
+
>
|
|
968
|
+
{{ t('panels.stepDetail.approveAndProceed') }}
|
|
969
|
+
</UButton>
|
|
970
|
+
<UButton
|
|
971
|
+
v-if="proposalEditableNow"
|
|
972
|
+
color="primary"
|
|
973
|
+
variant="soft"
|
|
974
|
+
size="sm"
|
|
975
|
+
icon="i-lucide-pencil"
|
|
976
|
+
block
|
|
977
|
+
:disabled="rejectArmed || submitting || !!gateRefusal"
|
|
978
|
+
@click="startEditing"
|
|
979
|
+
>
|
|
980
|
+
{{ t('panels.stepDetail.approveWithCorrections') }}
|
|
981
|
+
</UButton>
|
|
982
|
+
<p
|
|
983
|
+
v-else-if="!proposalEditable"
|
|
984
|
+
class="text-[10px] text-slate-500"
|
|
985
|
+
data-testid="step-rendered-output-note"
|
|
986
|
+
>
|
|
987
|
+
{{ t('panels.stepDetail.renderedOutputNote') }}
|
|
988
|
+
</p>
|
|
989
|
+
<!-- Withheld only until the quorum is one approval away, so it says so rather than
|
|
990
|
+
leaving a reviewer to wonder where the affordance went. -->
|
|
991
|
+
<p v-else class="text-[10px] text-slate-500" data-testid="step-quorum-edit-locked">
|
|
992
|
+
{{ t('panels.stepDetail.quorumEditLocked') }}
|
|
993
|
+
</p>
|
|
994
|
+
|
|
995
|
+
<!-- destructive: a two-step inline confirm instead of a native dialog -->
|
|
996
|
+
<div v-if="rejectArmed" class="rounded-lg border border-rose-500/40 bg-rose-500/5 p-2.5">
|
|
997
|
+
<p class="mb-2 text-[11px] text-rose-200">
|
|
998
|
+
{{ t('panels.stepDetail.rejectConfirmPrompt') }}
|
|
999
|
+
</p>
|
|
1000
|
+
<div class="flex gap-2">
|
|
945
1001
|
<UButton
|
|
946
1002
|
color="neutral"
|
|
947
1003
|
variant="ghost"
|
|
948
|
-
size="
|
|
949
|
-
|
|
1004
|
+
size="xs"
|
|
1005
|
+
class="flex-1"
|
|
950
1006
|
:disabled="submitting"
|
|
951
|
-
@click="
|
|
1007
|
+
@click="disarmReject"
|
|
952
1008
|
>
|
|
953
|
-
{{ t('
|
|
1009
|
+
{{ t('common.cancel') }}
|
|
954
1010
|
</UButton>
|
|
955
|
-
</div>
|
|
956
|
-
|
|
957
|
-
<div
|
|
958
|
-
v-else
|
|
959
|
-
class="space-y-2 border-t border-slate-800 px-4 pt-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))]"
|
|
960
|
-
>
|
|
961
1011
|
<UButton
|
|
962
|
-
color="
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
block
|
|
967
|
-
:disabled="rejectArmed || !!gateRefusal"
|
|
1012
|
+
color="error"
|
|
1013
|
+
size="xs"
|
|
1014
|
+
icon="i-lucide-ban"
|
|
1015
|
+
class="flex-1"
|
|
968
1016
|
:loading="submitting"
|
|
969
|
-
@click="
|
|
970
|
-
>
|
|
971
|
-
{{ t('panels.stepDetail.approveAndProceed') }}
|
|
972
|
-
</UButton>
|
|
973
|
-
<UButton
|
|
974
|
-
v-if="proposalEditableNow"
|
|
975
|
-
color="primary"
|
|
976
|
-
variant="soft"
|
|
977
|
-
size="sm"
|
|
978
|
-
icon="i-lucide-pencil"
|
|
979
|
-
block
|
|
980
|
-
:disabled="rejectArmed || submitting || !!gateRefusal"
|
|
981
|
-
@click="startEditing"
|
|
1017
|
+
@click="reject"
|
|
982
1018
|
>
|
|
983
|
-
{{ t('panels.stepDetail.
|
|
1019
|
+
{{ t('panels.stepDetail.confirmReject') }}
|
|
984
1020
|
</UButton>
|
|
985
|
-
<p
|
|
986
|
-
v-else-if="!proposalEditable"
|
|
987
|
-
class="text-[10px] text-slate-500"
|
|
988
|
-
data-testid="step-rendered-output-note"
|
|
989
|
-
>
|
|
990
|
-
{{ t('panels.stepDetail.renderedOutputNote') }}
|
|
991
|
-
</p>
|
|
992
|
-
<!-- Withheld only until the quorum is one approval away, so it says so rather than
|
|
993
|
-
leaving a reviewer to wonder where the affordance went. -->
|
|
994
|
-
<p v-else class="text-[10px] text-slate-500" data-testid="step-quorum-edit-locked">
|
|
995
|
-
{{ t('panels.stepDetail.quorumEditLocked') }}
|
|
996
|
-
</p>
|
|
997
|
-
|
|
998
|
-
<!-- destructive: a two-step inline confirm instead of a native dialog -->
|
|
999
|
-
<div
|
|
1000
|
-
v-if="rejectArmed"
|
|
1001
|
-
class="rounded-lg border border-rose-500/40 bg-rose-500/5 p-2.5"
|
|
1002
|
-
>
|
|
1003
|
-
<p class="mb-2 text-[11px] text-rose-200">
|
|
1004
|
-
{{ t('panels.stepDetail.rejectConfirmPrompt') }}
|
|
1005
|
-
</p>
|
|
1006
|
-
<div class="flex gap-2">
|
|
1007
|
-
<UButton
|
|
1008
|
-
color="neutral"
|
|
1009
|
-
variant="ghost"
|
|
1010
|
-
size="xs"
|
|
1011
|
-
class="flex-1"
|
|
1012
|
-
:disabled="submitting"
|
|
1013
|
-
@click="disarmReject"
|
|
1014
|
-
>
|
|
1015
|
-
{{ t('common.cancel') }}
|
|
1016
|
-
</UButton>
|
|
1017
|
-
<UButton
|
|
1018
|
-
color="error"
|
|
1019
|
-
size="xs"
|
|
1020
|
-
icon="i-lucide-ban"
|
|
1021
|
-
class="flex-1"
|
|
1022
|
-
:loading="submitting"
|
|
1023
|
-
@click="reject"
|
|
1024
|
-
>
|
|
1025
|
-
{{ t('panels.stepDetail.confirmReject') }}
|
|
1026
|
-
</UButton>
|
|
1027
|
-
</div>
|
|
1028
|
-
</div>
|
|
1029
|
-
<div v-else class="flex gap-2">
|
|
1030
|
-
<UButton
|
|
1031
|
-
color="warning"
|
|
1032
|
-
variant="soft"
|
|
1033
|
-
size="sm"
|
|
1034
|
-
icon="i-lucide-rotate-ccw"
|
|
1035
|
-
class="flex-1"
|
|
1036
|
-
data-testid="step-request-changes"
|
|
1037
|
-
:disabled="!canRequestChanges || !!gateRefusal"
|
|
1038
|
-
:loading="submitting"
|
|
1039
|
-
@click="requestChanges"
|
|
1040
|
-
>
|
|
1041
|
-
{{ t('panels.stepDetail.requestChanges') }}
|
|
1042
|
-
</UButton>
|
|
1043
|
-
<UButton
|
|
1044
|
-
color="error"
|
|
1045
|
-
variant="soft"
|
|
1046
|
-
size="sm"
|
|
1047
|
-
icon="i-lucide-ban"
|
|
1048
|
-
class="flex-1"
|
|
1049
|
-
:disabled="submitting || !!gateRefusal"
|
|
1050
|
-
@click="armReject"
|
|
1051
|
-
>
|
|
1052
|
-
{{ t('panels.stepDetail.reject') }}
|
|
1053
|
-
</UButton>
|
|
1054
|
-
</div>
|
|
1055
|
-
<p class="text-[10px] text-slate-500">
|
|
1056
|
-
{{ t('panels.stepDetail.requestChangesHint') }}
|
|
1057
|
-
</p>
|
|
1058
1021
|
</div>
|
|
1059
|
-
</
|
|
1022
|
+
</div>
|
|
1023
|
+
<div v-else class="flex gap-2">
|
|
1024
|
+
<UButton
|
|
1025
|
+
color="warning"
|
|
1026
|
+
variant="soft"
|
|
1027
|
+
size="sm"
|
|
1028
|
+
icon="i-lucide-rotate-ccw"
|
|
1029
|
+
class="flex-1"
|
|
1030
|
+
data-testid="step-request-changes"
|
|
1031
|
+
:disabled="!canRequestChanges || !!gateRefusal"
|
|
1032
|
+
:loading="submitting"
|
|
1033
|
+
@click="requestChanges"
|
|
1034
|
+
>
|
|
1035
|
+
{{ t('panels.stepDetail.requestChanges') }}
|
|
1036
|
+
</UButton>
|
|
1037
|
+
<UButton
|
|
1038
|
+
color="error"
|
|
1039
|
+
variant="soft"
|
|
1040
|
+
size="sm"
|
|
1041
|
+
icon="i-lucide-ban"
|
|
1042
|
+
class="flex-1"
|
|
1043
|
+
:disabled="submitting || !!gateRefusal"
|
|
1044
|
+
@click="armReject"
|
|
1045
|
+
>
|
|
1046
|
+
{{ t('panels.stepDetail.reject') }}
|
|
1047
|
+
</UButton>
|
|
1048
|
+
</div>
|
|
1049
|
+
<p class="text-[10px] text-slate-500">
|
|
1050
|
+
{{ t('panels.stepDetail.requestChangesHint') }}
|
|
1051
|
+
</p>
|
|
1060
1052
|
</div>
|
|
1061
|
-
</
|
|
1062
|
-
</
|
|
1053
|
+
</aside>
|
|
1054
|
+
</div>
|
|
1063
1055
|
</template>
|
|
1064
1056
|
|
|
1065
1057
|
<style scoped>
|