@cat-factory/app 0.138.1 → 0.139.0
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/brainstorm/BrainstormWindow.vue +338 -374
- package/app/components/clarity/ClarityReviewWindow.vue +323 -353
- package/app/components/consensus/ConsensusSessionWindow.vue +138 -150
- package/app/components/docs/DocInterviewWindow.vue +108 -136
- package/app/components/followUp/FollowUpWindow.vue +3 -5
- package/app/components/forkDecision/ForkDecisionWindow.vue +1 -3
- package/app/components/gates/GateResultView.vue +3 -5
- package/app/components/humanTest/HumanTestWindow.vue +3 -5
- package/app/components/initiative/InitiativePlanningWindow.vue +89 -112
- package/app/components/initiative/InitiativeTrackerWindow.vue +389 -424
- package/app/components/panels/GenericStructuredResultView.vue +3 -5
- package/app/components/panels/MergerResultView.vue +3 -5
- package/app/components/panels/ResultWindowShell.vue +1 -1
- package/app/components/prReview/PrReviewWindow.vue +181 -205
- package/app/components/ralph/RalphLoopResultView.vue +3 -5
- package/app/components/requirements/RequirementsReviewWindow.vue +512 -557
- package/app/components/spec/ServiceSpecWindow.vue +235 -266
- package/app/components/testing/TestReportWindow.vue +4 -6
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +3 -5
- package/app/composables/useResultView.ts +10 -21
- package/package.json +1 -1
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
// document — not the original description + linked docs/tasks — is what every downstream
|
|
9
9
|
// agent step and the spec-writer consume.
|
|
10
10
|
import { parseOutputOutline } from '~/utils/agentOutput'
|
|
11
|
-
import StepRestartControl from '~/components/panels/StepRestartControl.vue'
|
|
12
11
|
import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
|
|
13
|
-
import
|
|
12
|
+
import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
|
|
14
13
|
import type {
|
|
15
14
|
RequirementRecommendation,
|
|
16
15
|
RequirementReview,
|
|
@@ -556,624 +555,580 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
556
555
|
</script>
|
|
557
556
|
|
|
558
557
|
<template>
|
|
559
|
-
<
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
558
|
+
<ResultWindowShell
|
|
559
|
+
:open="open"
|
|
560
|
+
icon="i-lucide-clipboard-check"
|
|
561
|
+
icon-class="bg-indigo-500/15 text-indigo-300"
|
|
562
|
+
:title="t('requirements.title')"
|
|
563
|
+
:subtitle="block?.title"
|
|
564
|
+
:step-ref="{ instanceId, stepIndex }"
|
|
565
|
+
variant="centered"
|
|
566
|
+
width="5xl"
|
|
567
|
+
@close="close"
|
|
568
|
+
>
|
|
569
|
+
<template v-if="review" #header-extras>
|
|
570
|
+
<UBadge color="neutral" variant="subtle" size="sm">
|
|
571
|
+
{{ t('requirements.iteration', { current: iteration, max: maxIterations }) }}
|
|
572
|
+
</UBadge>
|
|
573
|
+
</template>
|
|
574
|
+
|
|
575
|
+
<div class="flex min-h-0 flex-1 flex-col lg:flex-row">
|
|
576
|
+
<!-- main column -->
|
|
577
|
+
<div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
|
|
578
|
+
<i18n-t
|
|
579
|
+
keypath="requirements.intro"
|
|
580
|
+
tag="p"
|
|
581
|
+
class="mb-4 text-sm text-slate-400"
|
|
582
|
+
scope="global"
|
|
583
|
+
>
|
|
584
|
+
<template #level>{{ block?.level ?? t('requirements.levelFallback') }}</template>
|
|
585
|
+
<template #answer
|
|
586
|
+
><span class="text-slate-300">{{ t('requirements.answerVerb') }}</span></template
|
|
587
|
+
>
|
|
588
|
+
<template #dismiss
|
|
589
|
+
><span class="text-slate-300">{{ t('requirements.dismissVerb') }}</span></template
|
|
590
|
+
>
|
|
591
|
+
</i18n-t>
|
|
592
|
+
|
|
593
|
+
<!-- empty state — the reviewer runs automatically as the first pipeline
|
|
594
|
+
gate step, so there's nothing to do here until then -->
|
|
595
|
+
<div
|
|
596
|
+
v-if="!review && !busy && !loading"
|
|
597
|
+
class="rounded-lg border border-dashed border-slate-700 p-8 text-center text-sm text-slate-500"
|
|
598
|
+
>
|
|
599
|
+
{{ t('requirements.empty') }}
|
|
600
|
+
</div>
|
|
601
|
+
|
|
602
|
+
<!-- working state (initial fetch on open, or a reviewer pass running) -->
|
|
603
|
+
<div
|
|
604
|
+
v-else-if="(busy || loading) && !review"
|
|
605
|
+
class="flex items-center justify-center gap-2 p-8 text-sm text-slate-400"
|
|
606
|
+
>
|
|
607
|
+
<UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
|
|
608
|
+
{{ loading && !busy ? t('requirements.loadingReview') : t('requirements.reviewing') }}
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
<template v-else-if="review">
|
|
612
|
+
<!-- converged: reviewer satisfied -->
|
|
572
613
|
<div
|
|
573
|
-
|
|
614
|
+
v-if="incorporated"
|
|
615
|
+
class="mb-4 flex items-center gap-2 rounded-lg border border-emerald-900/60 bg-emerald-950/30 p-4 text-sm text-emerald-300"
|
|
574
616
|
>
|
|
575
|
-
<UIcon name="i-lucide-
|
|
576
|
-
|
|
577
|
-
<div class="min-w-0">
|
|
578
|
-
<h1 class="truncate text-base font-semibold text-white">
|
|
579
|
-
{{ t('requirements.title') }}
|
|
580
|
-
</h1>
|
|
581
|
-
<p v-if="block" class="truncate text-xs text-slate-500">{{ block.title }}</p>
|
|
617
|
+
<UIcon name="i-lucide-circle-check" class="h-5 w-5 shrink-0" />
|
|
618
|
+
{{ t('requirements.settled') }}
|
|
582
619
|
</div>
|
|
583
|
-
<div class="ms-auto flex items-center gap-1.5">
|
|
584
|
-
<UBadge v-if="review" color="neutral" variant="subtle" size="sm">
|
|
585
|
-
{{ t('requirements.iteration', { current: iteration, max: maxIterations }) }}
|
|
586
|
-
</UBadge>
|
|
587
|
-
<StepRestartControl
|
|
588
|
-
:instance-id="instanceId"
|
|
589
|
-
:step-index="stepIndex"
|
|
590
|
-
@restarted="close"
|
|
591
|
-
/>
|
|
592
|
-
<IconButton
|
|
593
|
-
icon="i-lucide-x"
|
|
594
|
-
color="neutral"
|
|
595
|
-
variant="ghost"
|
|
596
|
-
size="sm"
|
|
597
|
-
:label="t('common.close')"
|
|
598
|
-
@click="close"
|
|
599
|
-
/>
|
|
600
|
-
</div>
|
|
601
|
-
</header>
|
|
602
620
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
<template #level>{{ block?.level ?? t('requirements.levelFallback') }}</template>
|
|
613
|
-
<template #answer
|
|
614
|
-
><span class="text-slate-300">{{ t('requirements.answerVerb') }}</span></template
|
|
615
|
-
>
|
|
616
|
-
<template #dismiss
|
|
617
|
-
><span class="text-slate-300">{{ t('requirements.dismissVerb') }}</span></template
|
|
618
|
-
>
|
|
619
|
-
</i18n-t>
|
|
621
|
+
<!-- iteration cap hit -->
|
|
622
|
+
<IterationCapPrompt
|
|
623
|
+
v-else-if="exceeded"
|
|
624
|
+
class="mb-4"
|
|
625
|
+
:heading="t('requirements.exceeded.heading', { max: maxIterations })"
|
|
626
|
+
:detail="t('requirements.exceeded.detail')"
|
|
627
|
+
:loading="acting"
|
|
628
|
+
@resolve="resolveExceeded"
|
|
629
|
+
/>
|
|
620
630
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
631
|
+
<!-- working: the async cycle is running in the driver. Two distinct stages so
|
|
632
|
+
the human can see which of the two LLM calls is currently in progress. -->
|
|
633
|
+
<div
|
|
634
|
+
v-else-if="working"
|
|
635
|
+
class="mb-4 flex items-center gap-2 rounded-lg border border-indigo-900/60 bg-indigo-950/30 p-4 text-sm text-indigo-200"
|
|
636
|
+
>
|
|
637
|
+
<UIcon name="i-lucide-loader-circle" class="h-5 w-5 shrink-0 animate-spin" />
|
|
638
|
+
<span v-if="incorporating">
|
|
639
|
+
{{ t('requirements.working.incorporating') }}
|
|
640
|
+
</span>
|
|
641
|
+
<span v-else>
|
|
642
|
+
{{ t('requirements.working.reReviewing') }}
|
|
643
|
+
</span>
|
|
644
|
+
</div>
|
|
629
645
|
|
|
630
|
-
|
|
646
|
+
<!-- findings to react to -->
|
|
647
|
+
<div v-if="review.items.length" class="flex flex-col gap-3">
|
|
631
648
|
<div
|
|
632
|
-
v-
|
|
633
|
-
|
|
649
|
+
v-for="item in sortedItems"
|
|
650
|
+
:key="item.id"
|
|
651
|
+
class="rounded-lg border border-slate-800 bg-slate-900/60 p-3"
|
|
652
|
+
:class="{ 'opacity-60': item.status === 'dismissed' }"
|
|
634
653
|
>
|
|
635
|
-
<
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
654
|
+
<div class="flex items-start gap-2">
|
|
655
|
+
<UIcon
|
|
656
|
+
:name="CATEGORY_ICON[item.category]"
|
|
657
|
+
class="mt-0.5 h-4 w-4 shrink-0 text-slate-400"
|
|
658
|
+
/>
|
|
659
|
+
<div class="min-w-0 flex-1">
|
|
660
|
+
<div class="flex flex-wrap items-center gap-1.5">
|
|
661
|
+
<span class="text-sm font-medium text-white">{{ item.title }}</span>
|
|
662
|
+
<UBadge size="xs" variant="subtle" :color="SEVERITY_COLOR[item.severity]">
|
|
663
|
+
{{ SEVERITY_LABELS[item.severity] }}
|
|
664
|
+
</UBadge>
|
|
665
|
+
<UBadge size="xs" variant="outline" color="neutral">
|
|
666
|
+
{{ CATEGORY_LABELS[item.category] }}
|
|
667
|
+
</UBadge>
|
|
668
|
+
<!-- Once the automation has pre-answered some findings, flag the ones it
|
|
669
|
+
left open as the genuine business decisions that need the human. -->
|
|
670
|
+
<UBadge
|
|
671
|
+
v-if="
|
|
672
|
+
hasAutoDefaults && item.status === 'open' && item.autoAnswerable === false
|
|
673
|
+
"
|
|
674
|
+
size="xs"
|
|
675
|
+
variant="subtle"
|
|
676
|
+
color="warning"
|
|
677
|
+
>
|
|
678
|
+
{{ t('requirements.needsYourInput') }}
|
|
679
|
+
</UBadge>
|
|
680
|
+
<UBadge
|
|
681
|
+
size="xs"
|
|
682
|
+
variant="soft"
|
|
683
|
+
:color="STATUS_COLOR[item.status]"
|
|
684
|
+
class="ms-auto"
|
|
685
|
+
>
|
|
686
|
+
{{ STATUS_LABELS[item.status] }}
|
|
687
|
+
</UBadge>
|
|
688
|
+
</div>
|
|
689
|
+
<p class="mt-1 whitespace-pre-line text-sm text-slate-400">
|
|
690
|
+
{{ item.detail }}
|
|
691
|
+
</p>
|
|
648
692
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
693
|
+
<!-- recorded answer (only for non-editable findings — for editable
|
|
694
|
+
ones the answer lives in the textarea below, seeded from the reply) -->
|
|
695
|
+
<div
|
|
696
|
+
v-if="item.reply && item.status !== 'open' && item.status !== 'answered'"
|
|
697
|
+
class="mt-2 rounded-md border-s-2 border-slate-700 bg-slate-950/40 px-3 py-1.5 text-sm text-slate-300"
|
|
698
|
+
>
|
|
699
|
+
<span class="text-[10px] uppercase tracking-wide text-slate-500">
|
|
700
|
+
{{ t('requirements.answerLabel') }}
|
|
701
|
+
</span>
|
|
702
|
+
<p class="whitespace-pre-line">{{ item.reply }}</p>
|
|
703
|
+
</div>
|
|
658
704
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
705
|
+
<!-- per-finding 3-way selector: Answer (write it) / Dismiss (irrelevant) /
|
|
706
|
+
Recommend (let the Requirement Writer suggest one). The active mode
|
|
707
|
+
drives the content below, IN PLACE — no separate section. Disabled once
|
|
708
|
+
the requirements are settled / a cycle is running; hidden for a
|
|
709
|
+
`resolved` finding (its recorded answer shows above). -->
|
|
710
|
+
<template v-if="item.status !== 'resolved'">
|
|
711
|
+
<div class="mt-2 flex flex-wrap items-center gap-1">
|
|
712
|
+
<UButton
|
|
713
|
+
v-for="opt in FINDING_MODES"
|
|
714
|
+
:key="opt.mode"
|
|
715
|
+
:color="modeFor(item) === opt.mode ? 'primary' : 'neutral'"
|
|
716
|
+
:variant="modeFor(item) === opt.mode ? 'soft' : 'ghost'"
|
|
717
|
+
size="xs"
|
|
718
|
+
:icon="opt.icon"
|
|
719
|
+
:disabled="frozen"
|
|
720
|
+
@click="setMode(item, opt.mode)"
|
|
721
|
+
>
|
|
722
|
+
{{ t(opt.labelKey) }}
|
|
723
|
+
<UIcon
|
|
724
|
+
v-if="opt.mode === 'answer' && isUserAnswered(item)"
|
|
725
|
+
name="i-lucide-check"
|
|
726
|
+
class="h-3.5 w-3.5 text-emerald-400"
|
|
727
|
+
/>
|
|
728
|
+
</UButton>
|
|
729
|
+
</div>
|
|
673
730
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
:name="CATEGORY_ICON[item.category]"
|
|
685
|
-
class="mt-0.5 h-4 w-4 shrink-0 text-slate-400"
|
|
686
|
-
/>
|
|
687
|
-
<div class="min-w-0 flex-1">
|
|
688
|
-
<div class="flex flex-wrap items-center gap-1.5">
|
|
689
|
-
<span class="text-sm font-medium text-white">{{ item.title }}</span>
|
|
690
|
-
<UBadge size="xs" variant="subtle" :color="SEVERITY_COLOR[item.severity]">
|
|
691
|
-
{{ SEVERITY_LABELS[item.severity] }}
|
|
692
|
-
</UBadge>
|
|
693
|
-
<UBadge size="xs" variant="outline" color="neutral">
|
|
694
|
-
{{ CATEGORY_LABELS[item.category] }}
|
|
695
|
-
</UBadge>
|
|
696
|
-
<!-- Once the automation has pre-answered some findings, flag the ones it
|
|
697
|
-
left open as the genuine business decisions that need the human. -->
|
|
731
|
+
<!-- ANSWER: type the answer directly (auto-saves on blur) -->
|
|
732
|
+
<template v-if="modeFor(item) === 'answer'">
|
|
733
|
+
<!-- Auto-generated recommended default: the automation pre-filled this
|
|
734
|
+
answer; the human can keep it, edit it, or switch modes. -->
|
|
735
|
+
<div
|
|
736
|
+
v-if="autoDefaults.get(item.id)"
|
|
737
|
+
class="mt-2 flex flex-wrap items-center gap-1.5 text-xs text-indigo-300"
|
|
738
|
+
>
|
|
739
|
+
<UIcon name="i-lucide-sparkles" class="h-3.5 w-3.5 shrink-0" />
|
|
740
|
+
<span>{{ t('requirements.recommendedDefault') }}</span>
|
|
698
741
|
<UBadge
|
|
699
|
-
v-if="
|
|
700
|
-
hasAutoDefaults &&
|
|
701
|
-
item.status === 'open' &&
|
|
702
|
-
item.autoAnswerable === false
|
|
703
|
-
"
|
|
742
|
+
v-if="autoDefaults.get(item.id)!.groundedInFragment"
|
|
704
743
|
size="xs"
|
|
705
744
|
variant="subtle"
|
|
706
|
-
color="
|
|
745
|
+
color="primary"
|
|
707
746
|
>
|
|
708
|
-
{{
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
:color="STATUS_COLOR[item.status]"
|
|
714
|
-
class="ms-auto"
|
|
715
|
-
>
|
|
716
|
-
{{ STATUS_LABELS[item.status] }}
|
|
747
|
+
{{
|
|
748
|
+
t('requirements.currentStandard', {
|
|
749
|
+
title: autoDefaults.get(item.id)!.groundedInFragment!.title,
|
|
750
|
+
})
|
|
751
|
+
}}
|
|
717
752
|
</UBadge>
|
|
718
753
|
</div>
|
|
719
|
-
<
|
|
720
|
-
|
|
754
|
+
<UTextarea
|
|
755
|
+
v-model="drafts[item.id]"
|
|
756
|
+
:rows="2"
|
|
757
|
+
autoresize
|
|
758
|
+
size="sm"
|
|
759
|
+
class="mt-2 w-full"
|
|
760
|
+
:placeholder="t('requirements.answerPlaceholder')"
|
|
761
|
+
:disabled="frozen"
|
|
762
|
+
@blur="persistDraft(item)"
|
|
763
|
+
/>
|
|
764
|
+
<p
|
|
765
|
+
v-if="isUserAnswered(item)"
|
|
766
|
+
class="mt-1 flex items-center gap-1 text-[11px] text-emerald-400"
|
|
767
|
+
>
|
|
768
|
+
<UIcon name="i-lucide-check" class="h-3 w-3 shrink-0" />
|
|
769
|
+
{{ t('requirements.userAnswered') }}
|
|
721
770
|
</p>
|
|
771
|
+
</template>
|
|
722
772
|
|
|
723
|
-
|
|
724
|
-
|
|
773
|
+
<!-- RECOMMEND: generating / the ready suggestion / a guidance box, all
|
|
774
|
+
rendered inline where the question was asked. -->
|
|
775
|
+
<template v-else-if="modeFor(item) === 'recommend'">
|
|
725
776
|
<div
|
|
726
|
-
v-if="item
|
|
727
|
-
class="mt-2
|
|
777
|
+
v-if="pendingRecFor(item)"
|
|
778
|
+
class="mt-2 flex items-center gap-1.5 text-xs text-indigo-300"
|
|
728
779
|
>
|
|
729
|
-
<
|
|
730
|
-
|
|
731
|
-
</span>
|
|
732
|
-
<p class="whitespace-pre-line">{{ item.reply }}</p>
|
|
780
|
+
<UIcon name="i-lucide-loader-circle" class="h-3.5 w-3.5 animate-spin" />
|
|
781
|
+
{{ t('requirements.generatingSuggestion') }}
|
|
733
782
|
</div>
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
<UButton
|
|
743
|
-
v-for="opt in FINDING_MODES"
|
|
744
|
-
:key="opt.mode"
|
|
745
|
-
:color="modeFor(item) === opt.mode ? 'primary' : 'neutral'"
|
|
746
|
-
:variant="modeFor(item) === opt.mode ? 'soft' : 'ghost'"
|
|
783
|
+
<template v-else-if="readyRecFor(item)">
|
|
784
|
+
<div
|
|
785
|
+
v-for="rec in [readyRecFor(item)!]"
|
|
786
|
+
:key="rec.id"
|
|
787
|
+
class="mt-2 rounded-lg border border-indigo-900/50 bg-indigo-950/20 p-3"
|
|
788
|
+
>
|
|
789
|
+
<UBadge
|
|
790
|
+
v-if="rec.groundedInFragment"
|
|
747
791
|
size="xs"
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
792
|
+
variant="subtle"
|
|
793
|
+
color="success"
|
|
794
|
+
icon="i-lucide-badge-check"
|
|
751
795
|
>
|
|
752
|
-
{{
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
<!-- Auto-generated recommended default: the automation pre-filled this
|
|
764
|
-
answer; the human can keep it, edit it, or switch modes. -->
|
|
765
|
-
<div
|
|
766
|
-
v-if="autoDefaults.get(item.id)"
|
|
767
|
-
class="mt-2 flex flex-wrap items-center gap-1.5 text-xs text-indigo-300"
|
|
768
|
-
>
|
|
769
|
-
<UIcon name="i-lucide-sparkles" class="h-3.5 w-3.5 shrink-0" />
|
|
770
|
-
<span>{{ t('requirements.recommendedDefault') }}</span>
|
|
771
|
-
<UBadge
|
|
772
|
-
v-if="autoDefaults.get(item.id)!.groundedInFragment"
|
|
773
|
-
size="xs"
|
|
774
|
-
variant="subtle"
|
|
796
|
+
{{
|
|
797
|
+
t('requirements.currentStandard', {
|
|
798
|
+
title: rec.groundedInFragment.title,
|
|
799
|
+
})
|
|
800
|
+
}}
|
|
801
|
+
</UBadge>
|
|
802
|
+
<p class="mt-1 whitespace-pre-line text-sm text-slate-300">
|
|
803
|
+
{{ rec.recommendedText }}
|
|
804
|
+
</p>
|
|
805
|
+
<div class="mt-2 flex flex-wrap items-center gap-2">
|
|
806
|
+
<UButton
|
|
775
807
|
color="primary"
|
|
808
|
+
variant="soft"
|
|
809
|
+
size="xs"
|
|
810
|
+
icon="i-lucide-check"
|
|
811
|
+
:disabled="frozen || !access.canExecuteRuns.value"
|
|
812
|
+
:title="
|
|
813
|
+
access.canExecuteRuns.value ? undefined : t('access.noRunExecute')
|
|
814
|
+
"
|
|
815
|
+
@click="acceptRecommendation(rec)"
|
|
776
816
|
>
|
|
777
|
-
{{
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
class="mt-2 w-full"
|
|
790
|
-
:placeholder="t('requirements.answerPlaceholder')"
|
|
791
|
-
:disabled="frozen"
|
|
792
|
-
@blur="persistDraft(item)"
|
|
793
|
-
/>
|
|
794
|
-
<p
|
|
795
|
-
v-if="isUserAnswered(item)"
|
|
796
|
-
class="mt-1 flex items-center gap-1 text-[11px] text-emerald-400"
|
|
797
|
-
>
|
|
798
|
-
<UIcon name="i-lucide-check" class="h-3 w-3 shrink-0" />
|
|
799
|
-
{{ t('requirements.userAnswered') }}
|
|
800
|
-
</p>
|
|
801
|
-
</template>
|
|
802
|
-
|
|
803
|
-
<!-- RECOMMEND: generating / the ready suggestion / a guidance box, all
|
|
804
|
-
rendered inline where the question was asked. -->
|
|
805
|
-
<template v-else-if="modeFor(item) === 'recommend'">
|
|
806
|
-
<div
|
|
807
|
-
v-if="pendingRecFor(item)"
|
|
808
|
-
class="mt-2 flex items-center gap-1.5 text-xs text-indigo-300"
|
|
809
|
-
>
|
|
810
|
-
<UIcon name="i-lucide-loader-circle" class="h-3.5 w-3.5 animate-spin" />
|
|
811
|
-
{{ t('requirements.generatingSuggestion') }}
|
|
812
|
-
</div>
|
|
813
|
-
<template v-else-if="readyRecFor(item)">
|
|
814
|
-
<div
|
|
815
|
-
v-for="rec in [readyRecFor(item)!]"
|
|
816
|
-
:key="rec.id"
|
|
817
|
-
class="mt-2 rounded-lg border border-indigo-900/50 bg-indigo-950/20 p-3"
|
|
817
|
+
{{ t('requirements.accept') }}
|
|
818
|
+
</UButton>
|
|
819
|
+
<UButton
|
|
820
|
+
color="neutral"
|
|
821
|
+
variant="ghost"
|
|
822
|
+
size="xs"
|
|
823
|
+
icon="i-lucide-x"
|
|
824
|
+
:disabled="frozen || !access.canExecuteRuns.value"
|
|
825
|
+
:title="
|
|
826
|
+
access.canExecuteRuns.value ? undefined : t('access.noRunExecute')
|
|
827
|
+
"
|
|
828
|
+
@click="rejectRecommendation(rec)"
|
|
818
829
|
>
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
color="success"
|
|
824
|
-
icon="i-lucide-badge-check"
|
|
825
|
-
>
|
|
826
|
-
{{
|
|
827
|
-
t('requirements.currentStandard', {
|
|
828
|
-
title: rec.groundedInFragment.title,
|
|
829
|
-
})
|
|
830
|
-
}}
|
|
831
|
-
</UBadge>
|
|
832
|
-
<p class="mt-1 whitespace-pre-line text-sm text-slate-300">
|
|
833
|
-
{{ rec.recommendedText }}
|
|
834
|
-
</p>
|
|
835
|
-
<div class="mt-2 flex flex-wrap items-center gap-2">
|
|
836
|
-
<UButton
|
|
837
|
-
color="primary"
|
|
838
|
-
variant="soft"
|
|
839
|
-
size="xs"
|
|
840
|
-
icon="i-lucide-check"
|
|
841
|
-
:disabled="frozen || !access.canExecuteRuns.value"
|
|
842
|
-
:title="
|
|
843
|
-
access.canExecuteRuns.value
|
|
844
|
-
? undefined
|
|
845
|
-
: t('access.noRunExecute')
|
|
846
|
-
"
|
|
847
|
-
@click="acceptRecommendation(rec)"
|
|
848
|
-
>
|
|
849
|
-
{{ t('requirements.accept') }}
|
|
850
|
-
</UButton>
|
|
851
|
-
<UButton
|
|
852
|
-
color="neutral"
|
|
853
|
-
variant="ghost"
|
|
854
|
-
size="xs"
|
|
855
|
-
icon="i-lucide-x"
|
|
856
|
-
:disabled="frozen || !access.canExecuteRuns.value"
|
|
857
|
-
:title="
|
|
858
|
-
access.canExecuteRuns.value
|
|
859
|
-
? undefined
|
|
860
|
-
: t('access.noRunExecute')
|
|
861
|
-
"
|
|
862
|
-
@click="rejectRecommendation(rec)"
|
|
863
|
-
>
|
|
864
|
-
{{ t('requirements.reject') }}
|
|
865
|
-
</UButton>
|
|
866
|
-
</div>
|
|
867
|
-
<div class="mt-2 flex items-start gap-2">
|
|
868
|
-
<UTextarea
|
|
869
|
-
v-model="reRequestNotes[rec.id]"
|
|
870
|
-
:rows="1"
|
|
871
|
-
autoresize
|
|
872
|
-
size="sm"
|
|
873
|
-
class="flex-1"
|
|
874
|
-
:placeholder="t('requirements.reRequestPlaceholder')"
|
|
875
|
-
:disabled="frozen || recommending"
|
|
876
|
-
/>
|
|
877
|
-
<UButton
|
|
878
|
-
color="neutral"
|
|
879
|
-
variant="soft"
|
|
880
|
-
size="xs"
|
|
881
|
-
icon="i-lucide-rotate-cw"
|
|
882
|
-
:loading="recommending"
|
|
883
|
-
:disabled="
|
|
884
|
-
!(reRequestNotes[rec.id] ?? '').trim() ||
|
|
885
|
-
frozen ||
|
|
886
|
-
!access.canExecuteRuns.value
|
|
887
|
-
"
|
|
888
|
-
:title="
|
|
889
|
-
access.canExecuteRuns.value
|
|
890
|
-
? undefined
|
|
891
|
-
: t('access.noRunExecute')
|
|
892
|
-
"
|
|
893
|
-
@click="reRequestRecommendation(rec)"
|
|
894
|
-
>
|
|
895
|
-
{{ t('requirements.reRequest') }}
|
|
896
|
-
</UButton>
|
|
897
|
-
</div>
|
|
898
|
-
</div>
|
|
899
|
-
</template>
|
|
900
|
-
<template v-else>
|
|
830
|
+
{{ t('requirements.reject') }}
|
|
831
|
+
</UButton>
|
|
832
|
+
</div>
|
|
833
|
+
<div class="mt-2 flex items-start gap-2">
|
|
901
834
|
<UTextarea
|
|
902
|
-
v-model="
|
|
903
|
-
:rows="
|
|
835
|
+
v-model="reRequestNotes[rec.id]"
|
|
836
|
+
:rows="1"
|
|
904
837
|
autoresize
|
|
905
838
|
size="sm"
|
|
906
|
-
class="
|
|
907
|
-
:placeholder="t('requirements.
|
|
908
|
-
:disabled="frozen"
|
|
839
|
+
class="flex-1"
|
|
840
|
+
:placeholder="t('requirements.reRequestPlaceholder')"
|
|
841
|
+
:disabled="frozen || recommending"
|
|
909
842
|
/>
|
|
910
|
-
<
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
843
|
+
<UButton
|
|
844
|
+
color="neutral"
|
|
845
|
+
variant="soft"
|
|
846
|
+
size="xs"
|
|
847
|
+
icon="i-lucide-rotate-cw"
|
|
848
|
+
:loading="recommending"
|
|
849
|
+
:disabled="
|
|
850
|
+
!(reRequestNotes[rec.id] ?? '').trim() ||
|
|
851
|
+
frozen ||
|
|
852
|
+
!access.canExecuteRuns.value
|
|
853
|
+
"
|
|
854
|
+
:title="
|
|
855
|
+
access.canExecuteRuns.value ? undefined : t('access.noRunExecute')
|
|
856
|
+
"
|
|
857
|
+
@click="reRequestRecommendation(rec)"
|
|
858
|
+
>
|
|
859
|
+
{{ t('requirements.reRequest') }}
|
|
860
|
+
</UButton>
|
|
861
|
+
</div>
|
|
862
|
+
</div>
|
|
863
|
+
</template>
|
|
864
|
+
<template v-else>
|
|
865
|
+
<UTextarea
|
|
866
|
+
v-model="guidanceDrafts[item.id]"
|
|
867
|
+
:rows="2"
|
|
868
|
+
autoresize
|
|
869
|
+
size="sm"
|
|
870
|
+
class="mt-2 w-full"
|
|
871
|
+
:placeholder="t('requirements.guidancePlaceholder')"
|
|
872
|
+
:disabled="frozen"
|
|
873
|
+
/>
|
|
874
|
+
<p class="mt-1 flex items-center gap-1 text-[11px] text-indigo-300/80">
|
|
875
|
+
<UIcon name="i-lucide-wand-2" class="h-3 w-3 shrink-0" />
|
|
876
|
+
{{ t('requirements.guidanceHint') }}
|
|
920
877
|
</p>
|
|
921
878
|
</template>
|
|
922
|
-
</
|
|
923
|
-
|
|
879
|
+
</template>
|
|
880
|
+
|
|
881
|
+
<!-- DISMISS: nothing to fill in — a short note explains the effect -->
|
|
882
|
+
<p v-else class="mt-2 text-[11px] text-slate-500">
|
|
883
|
+
{{ t('requirements.dismissedHint') }}
|
|
884
|
+
</p>
|
|
885
|
+
</template>
|
|
924
886
|
</div>
|
|
925
887
|
</div>
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
926
890
|
|
|
927
|
-
|
|
891
|
+
<!-- incorporated document: the standard-format requirements. The whole section
|
|
928
892
|
collapses as a unit (a long doc otherwise pushes the findings/recommendations
|
|
929
893
|
off-screen); the per-heading toggles below still work when it's expanded. -->
|
|
930
|
-
|
|
894
|
+
<section v-if="outline" class="mt-6 border-t border-slate-800 pt-5">
|
|
895
|
+
<button
|
|
896
|
+
class="mb-3 flex w-full items-center gap-1.5 text-[11px] text-emerald-400"
|
|
897
|
+
@click="toggleDoc"
|
|
898
|
+
>
|
|
899
|
+
<UIcon
|
|
900
|
+
name="i-lucide-chevron-right"
|
|
901
|
+
class="h-3.5 w-3.5 shrink-0 transition-transform"
|
|
902
|
+
:class="docCollapsed ? '' : 'rotate-90'"
|
|
903
|
+
/>
|
|
904
|
+
<UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
|
|
905
|
+
<span class="font-semibold uppercase tracking-wide">
|
|
906
|
+
{{
|
|
907
|
+
incorporated
|
|
908
|
+
? t('requirements.finalRequirements')
|
|
909
|
+
: t('requirements.incorporatedDraft')
|
|
910
|
+
}}
|
|
911
|
+
</span>
|
|
912
|
+
</button>
|
|
913
|
+
<div v-show="!docCollapsed">
|
|
914
|
+
<div v-for="s in outline.sections" :key="s.id" class="mb-2">
|
|
931
915
|
<button
|
|
932
|
-
|
|
933
|
-
|
|
916
|
+
v-if="s.title"
|
|
917
|
+
class="group flex w-full items-center gap-2 text-start"
|
|
918
|
+
@click="toggle(s.id)"
|
|
934
919
|
>
|
|
935
920
|
<UIcon
|
|
936
921
|
name="i-lucide-chevron-right"
|
|
937
|
-
class="h-3.5 w-3.5 shrink-0 transition-transform"
|
|
938
|
-
:class="
|
|
922
|
+
class="h-3.5 w-3.5 shrink-0 text-slate-500 transition-transform"
|
|
923
|
+
:class="collapsed[s.id] ? '' : 'rotate-90'"
|
|
924
|
+
/>
|
|
925
|
+
<span
|
|
926
|
+
class="font-semibold text-white"
|
|
927
|
+
:class="s.depth <= 1 ? 'text-base' : s.depth === 2 ? 'text-sm' : 'text-xs'"
|
|
928
|
+
v-html="s.titleHtml"
|
|
939
929
|
/>
|
|
940
|
-
<UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
|
|
941
|
-
<span class="font-semibold uppercase tracking-wide">
|
|
942
|
-
{{
|
|
943
|
-
incorporated
|
|
944
|
-
? t('requirements.finalRequirements')
|
|
945
|
-
: t('requirements.incorporatedDraft')
|
|
946
|
-
}}
|
|
947
|
-
</span>
|
|
948
930
|
</button>
|
|
949
|
-
<div
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
:class="collapsed[s.id] ? '' : 'rotate-90'"
|
|
960
|
-
/>
|
|
961
|
-
<span
|
|
962
|
-
class="font-semibold text-white"
|
|
963
|
-
:class="s.depth <= 1 ? 'text-base' : s.depth === 2 ? 'text-sm' : 'text-xs'"
|
|
964
|
-
v-html="s.titleHtml"
|
|
965
|
-
/>
|
|
966
|
-
</button>
|
|
967
|
-
<div
|
|
968
|
-
v-show="!s.title || !collapsed[s.id]"
|
|
969
|
-
class="reader-prose mt-1 ps-5.5 text-[13px] leading-relaxed text-slate-300"
|
|
970
|
-
v-html="s.bodyHtml"
|
|
971
|
-
/>
|
|
972
|
-
</div>
|
|
973
|
-
</div>
|
|
974
|
-
</section>
|
|
975
|
-
</template>
|
|
976
|
-
</div>
|
|
931
|
+
<div
|
|
932
|
+
v-show="!s.title || !collapsed[s.id]"
|
|
933
|
+
class="reader-prose mt-1 ps-5.5 text-[13px] leading-relaxed text-slate-300"
|
|
934
|
+
v-html="s.bodyHtml"
|
|
935
|
+
/>
|
|
936
|
+
</div>
|
|
937
|
+
</div>
|
|
938
|
+
</section>
|
|
939
|
+
</template>
|
|
940
|
+
</div>
|
|
977
941
|
|
|
978
|
-
|
|
942
|
+
<!-- action rail: a right-hand column on wide screens, a bottom action bar below `lg`
|
|
979
943
|
(never hidden — the gate is otherwise unadvanceable on a laptop split-screen /
|
|
980
944
|
tablet, UX-32). The informational stats collapse away below `lg` to keep the
|
|
981
945
|
bottom bar compact; the actions themselves always show. -->
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
946
|
+
<aside
|
|
947
|
+
class="flex w-full shrink-0 flex-col border-t border-slate-800 lg:w-72 lg:border-s lg:border-t-0"
|
|
948
|
+
>
|
|
949
|
+
<div class="flex flex-col gap-4 px-4 py-5">
|
|
950
|
+
<div v-if="review" class="hidden space-y-2 text-xs text-slate-400 lg:block">
|
|
951
|
+
<div class="flex items-center justify-between">
|
|
952
|
+
<span>{{ t('requirements.stats.findings') }}</span>
|
|
953
|
+
<span class="text-slate-300">{{ review.items.length }}</span>
|
|
954
|
+
</div>
|
|
955
|
+
<div class="flex items-center justify-between">
|
|
956
|
+
<span>{{ t('requirements.stats.open') }}</span>
|
|
957
|
+
<span class="text-slate-300">{{ openCount }}</span>
|
|
958
|
+
</div>
|
|
959
|
+
<div class="flex items-center justify-between">
|
|
960
|
+
<span>{{ t('requirements.stats.answered') }}</span>
|
|
961
|
+
<span class="text-slate-300">{{ answeredCount }}</span>
|
|
962
|
+
</div>
|
|
963
|
+
<!-- awaited recommendations — kept here (always visible) so the human can see what
|
|
1000
964
|
the Writer is still producing / what's waiting on them even while reading the
|
|
1001
965
|
incorporated document or acting elsewhere in the window. -->
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
</div>
|
|
1020
|
-
</template>
|
|
1021
|
-
<div v-if="review.model" class="flex items-center justify-between">
|
|
1022
|
-
<span>{{ t('requirements.stats.model') }}</span>
|
|
1023
|
-
<span class="truncate ps-2 text-slate-500">{{
|
|
1024
|
-
models.labelForRef(review.model) ?? review.model
|
|
1025
|
-
}}</span>
|
|
1026
|
-
</div>
|
|
966
|
+
<template v-if="generatingRecommendations.length || readyRecommendations.length">
|
|
967
|
+
<div
|
|
968
|
+
class="flex items-center gap-1.5 border-t border-slate-800/60 pt-2 text-indigo-300"
|
|
969
|
+
>
|
|
970
|
+
<UIcon name="i-lucide-wand-2" class="h-3 w-3" />
|
|
971
|
+
<span class="font-medium">{{ t('requirements.stats.recommendations') }}</span>
|
|
972
|
+
</div>
|
|
973
|
+
<div
|
|
974
|
+
v-if="generatingRecommendations.length"
|
|
975
|
+
class="flex items-center justify-between"
|
|
976
|
+
>
|
|
977
|
+
<span>{{ t('requirements.stats.recsGenerating') }}</span>
|
|
978
|
+
<span class="text-indigo-300">{{ generatingRecommendations.length }}</span>
|
|
979
|
+
</div>
|
|
980
|
+
<div v-if="readyRecommendations.length" class="flex items-center justify-between">
|
|
981
|
+
<span>{{ t('requirements.stats.recsToReview') }}</span>
|
|
982
|
+
<span class="text-indigo-300">{{ readyRecommendations.length }}</span>
|
|
1027
983
|
</div>
|
|
984
|
+
</template>
|
|
985
|
+
<div v-if="review.model" class="flex items-center justify-between">
|
|
986
|
+
<span>{{ t('requirements.stats.model') }}</span>
|
|
987
|
+
<span class="truncate ps-2 text-slate-500">{{
|
|
988
|
+
models.labelForRef(review.model) ?? review.model
|
|
989
|
+
}}</span>
|
|
990
|
+
</div>
|
|
991
|
+
</div>
|
|
1028
992
|
|
|
1029
|
-
|
|
993
|
+
<!-- Request the Requirement Writer for the marked findings. Kept OUT of the
|
|
1030
994
|
status-scoped blocks below so it's available whenever the review is still
|
|
1031
995
|
editable — the `ready` first pass AND a `merged` review being reworked — not
|
|
1032
996
|
only when status is exactly `ready`. Scoped to exactly those two states (NOT a
|
|
1033
997
|
bare `!frozen`, which would also expose it in `exceeded`, where the run is parked
|
|
1034
998
|
on the cap decision and a fresh recommendation batch has no path to settle). -->
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
</UButton>
|
|
1060
|
-
</div>
|
|
1061
|
-
|
|
1062
|
-
<!-- action: ready (answer → incorporate / proceed) -->
|
|
1063
|
-
<div
|
|
1064
|
-
v-if="review && status === 'ready'"
|
|
1065
|
-
class="space-y-2 border-t border-slate-800 pt-4"
|
|
1066
|
-
>
|
|
1067
|
-
<UButton
|
|
1068
|
-
v-if="canProceed"
|
|
1069
|
-
color="primary"
|
|
1070
|
-
size="sm"
|
|
1071
|
-
block
|
|
1072
|
-
icon="i-lucide-arrow-right"
|
|
1073
|
-
:ui="{ leadingIcon: 'rtl:-scale-x-100', trailingIcon: 'rtl:-scale-x-100' }"
|
|
1074
|
-
:loading="acting"
|
|
1075
|
-
:disabled="!access.canExecuteRuns.value"
|
|
1076
|
-
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1077
|
-
@click="proceed"
|
|
1078
|
-
>
|
|
1079
|
-
{{ t('requirements.actions.proceedNothing') }}
|
|
1080
|
-
</UButton>
|
|
1081
|
-
<UButton
|
|
1082
|
-
v-else
|
|
1083
|
-
color="primary"
|
|
1084
|
-
size="sm"
|
|
1085
|
-
block
|
|
1086
|
-
icon="i-lucide-wand-sparkles"
|
|
1087
|
-
:loading="reworking"
|
|
1088
|
-
:disabled="!canIncorporate || !access.canExecuteRuns.value"
|
|
1089
|
-
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1090
|
-
@click="incorporate()"
|
|
1091
|
-
>
|
|
1092
|
-
{{ t('requirements.actions.incorporateAnswers') }}
|
|
1093
|
-
</UButton>
|
|
1094
|
-
<p class="text-[11px] leading-relaxed text-slate-500">
|
|
1095
|
-
<template v-if="canProceed">
|
|
1096
|
-
{{ t('requirements.help.canProceed') }}
|
|
1097
|
-
</template>
|
|
1098
|
-
<template v-else-if="canIncorporate">
|
|
1099
|
-
{{ t('requirements.help.canIncorporate') }}
|
|
1100
|
-
</template>
|
|
1101
|
-
<template v-else> {{ t('requirements.help.answerAll') }} </template>
|
|
1102
|
-
</p>
|
|
1103
|
-
</div>
|
|
999
|
+
<div
|
|
1000
|
+
v-if="review && pendingRecommendRequests.length > 0 && (status === 'ready' || merged)"
|
|
1001
|
+
class="border-t border-slate-800 pt-4"
|
|
1002
|
+
>
|
|
1003
|
+
<UButton
|
|
1004
|
+
color="primary"
|
|
1005
|
+
variant="soft"
|
|
1006
|
+
size="sm"
|
|
1007
|
+
block
|
|
1008
|
+
icon="i-lucide-wand-2"
|
|
1009
|
+
:loading="recommending"
|
|
1010
|
+
:disabled="!access.canExecuteRuns.value"
|
|
1011
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1012
|
+
@click="requestRecommendations"
|
|
1013
|
+
>
|
|
1014
|
+
{{
|
|
1015
|
+
t(
|
|
1016
|
+
'requirements.actions.requestRecommendations',
|
|
1017
|
+
{ count: pendingRecommendRequests.length },
|
|
1018
|
+
pendingRecommendRequests.length,
|
|
1019
|
+
)
|
|
1020
|
+
}}
|
|
1021
|
+
</UButton>
|
|
1022
|
+
</div>
|
|
1104
1023
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
:placeholder="t('requirements.redoPlaceholder')"
|
|
1145
|
-
/>
|
|
1146
|
-
<UButton
|
|
1147
|
-
color="primary"
|
|
1148
|
-
variant="soft"
|
|
1149
|
-
size="xs"
|
|
1150
|
-
block
|
|
1151
|
-
icon="i-lucide-wand-sparkles"
|
|
1152
|
-
:loading="reworking"
|
|
1153
|
-
:disabled="!redoComment.trim() || !access.canExecuteRuns.value"
|
|
1154
|
-
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1155
|
-
@click="incorporate(redoComment.trim())"
|
|
1156
|
-
>
|
|
1157
|
-
{{ t('requirements.actions.redoWithDirection') }}
|
|
1158
|
-
</UButton>
|
|
1159
|
-
</div>
|
|
1160
|
-
<p class="text-[11px] leading-relaxed text-slate-500">
|
|
1161
|
-
{{ t('requirements.help.merged') }}
|
|
1162
|
-
</p>
|
|
1163
|
-
</div>
|
|
1024
|
+
<!-- action: ready (answer → incorporate / proceed) -->
|
|
1025
|
+
<div v-if="review && status === 'ready'" class="space-y-2 border-t border-slate-800 pt-4">
|
|
1026
|
+
<UButton
|
|
1027
|
+
v-if="canProceed"
|
|
1028
|
+
color="primary"
|
|
1029
|
+
size="sm"
|
|
1030
|
+
block
|
|
1031
|
+
icon="i-lucide-arrow-right"
|
|
1032
|
+
:ui="{ leadingIcon: 'rtl:-scale-x-100', trailingIcon: 'rtl:-scale-x-100' }"
|
|
1033
|
+
:loading="acting"
|
|
1034
|
+
:disabled="!access.canExecuteRuns.value"
|
|
1035
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1036
|
+
@click="proceed"
|
|
1037
|
+
>
|
|
1038
|
+
{{ t('requirements.actions.proceedNothing') }}
|
|
1039
|
+
</UButton>
|
|
1040
|
+
<UButton
|
|
1041
|
+
v-else
|
|
1042
|
+
color="primary"
|
|
1043
|
+
size="sm"
|
|
1044
|
+
block
|
|
1045
|
+
icon="i-lucide-wand-sparkles"
|
|
1046
|
+
:loading="reworking"
|
|
1047
|
+
:disabled="!canIncorporate || !access.canExecuteRuns.value"
|
|
1048
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1049
|
+
@click="incorporate()"
|
|
1050
|
+
>
|
|
1051
|
+
{{ t('requirements.actions.incorporateAnswers') }}
|
|
1052
|
+
</UButton>
|
|
1053
|
+
<p class="text-[11px] leading-relaxed text-slate-500">
|
|
1054
|
+
<template v-if="canProceed">
|
|
1055
|
+
{{ t('requirements.help.canProceed') }}
|
|
1056
|
+
</template>
|
|
1057
|
+
<template v-else-if="canIncorporate">
|
|
1058
|
+
{{ t('requirements.help.canIncorporate') }}
|
|
1059
|
+
</template>
|
|
1060
|
+
<template v-else> {{ t('requirements.help.answerAll') }} </template>
|
|
1061
|
+
</p>
|
|
1062
|
+
</div>
|
|
1164
1063
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1064
|
+
<!-- action: merged (inspect → re-review / redo) -->
|
|
1065
|
+
<div v-if="review && merged" class="space-y-2 border-t border-slate-800 pt-4">
|
|
1066
|
+
<UButton
|
|
1067
|
+
color="primary"
|
|
1068
|
+
size="sm"
|
|
1069
|
+
block
|
|
1070
|
+
icon="i-lucide-sparkles"
|
|
1071
|
+
:loading="busy"
|
|
1072
|
+
:disabled="!access.canExecuteRuns.value"
|
|
1073
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1074
|
+
@click="reReview"
|
|
1075
|
+
>
|
|
1076
|
+
{{
|
|
1077
|
+
busy ? t('requirements.actions.reReviewing') : t('requirements.actions.reReview')
|
|
1078
|
+
}}
|
|
1079
|
+
</UButton>
|
|
1080
|
+
<UButton
|
|
1081
|
+
color="neutral"
|
|
1082
|
+
variant="soft"
|
|
1083
|
+
size="sm"
|
|
1084
|
+
block
|
|
1085
|
+
icon="i-lucide-pencil"
|
|
1086
|
+
@click="
|
|
1087
|
+
() => {
|
|
1088
|
+
showRedo = !showRedo
|
|
1089
|
+
}
|
|
1090
|
+
"
|
|
1091
|
+
>
|
|
1092
|
+
{{ t('requirements.actions.redoIncorporation') }}
|
|
1093
|
+
</UButton>
|
|
1094
|
+
<div v-if="showRedo" class="space-y-2">
|
|
1095
|
+
<UTextarea
|
|
1096
|
+
v-model="redoComment"
|
|
1097
|
+
:rows="3"
|
|
1098
|
+
autoresize
|
|
1099
|
+
size="sm"
|
|
1100
|
+
class="w-full"
|
|
1101
|
+
:placeholder="t('requirements.redoPlaceholder')"
|
|
1102
|
+
/>
|
|
1103
|
+
<UButton
|
|
1104
|
+
color="primary"
|
|
1105
|
+
variant="soft"
|
|
1106
|
+
size="xs"
|
|
1107
|
+
block
|
|
1108
|
+
icon="i-lucide-wand-sparkles"
|
|
1109
|
+
:loading="reworking"
|
|
1110
|
+
:disabled="!redoComment.trim() || !access.canExecuteRuns.value"
|
|
1111
|
+
:title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
|
|
1112
|
+
@click="incorporate(redoComment.trim())"
|
|
1168
1113
|
>
|
|
1169
|
-
{{ t('requirements.
|
|
1170
|
-
</
|
|
1114
|
+
{{ t('requirements.actions.redoWithDirection') }}
|
|
1115
|
+
</UButton>
|
|
1171
1116
|
</div>
|
|
1172
|
-
|
|
1117
|
+
<p class="text-[11px] leading-relaxed text-slate-500">
|
|
1118
|
+
{{ t('requirements.help.merged') }}
|
|
1119
|
+
</p>
|
|
1120
|
+
</div>
|
|
1121
|
+
|
|
1122
|
+
<div
|
|
1123
|
+
v-if="review && incorporated"
|
|
1124
|
+
class="border-t border-slate-800 pt-4 text-[11px] leading-relaxed text-slate-500"
|
|
1125
|
+
>
|
|
1126
|
+
{{ t('requirements.settledFooter') }}
|
|
1127
|
+
</div>
|
|
1173
1128
|
</div>
|
|
1174
|
-
</
|
|
1129
|
+
</aside>
|
|
1175
1130
|
</div>
|
|
1176
|
-
</
|
|
1131
|
+
</ResultWindowShell>
|
|
1177
1132
|
</template>
|
|
1178
1133
|
|
|
1179
1134
|
<style scoped>
|