@cat-factory/app 0.47.9 → 0.47.10
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 +96 -59
- package/app/components/clarity/ClarityReviewWindow.vue +84 -59
- package/app/components/consensus/ConsensusSessionWindow.vue +72 -34
- package/app/components/focus/BlockFocusView.vue +14 -10
- package/app/components/followUp/FollowUpWindow.vue +51 -26
- package/app/components/humanTest/HumanTestWindow.vue +70 -35
- package/app/components/requirements/RequirementsReviewWindow.vue +130 -82
- package/app/components/spec/ServiceSpecWindow.vue +54 -28
- package/app/components/testing/TestReportWindow.vue +82 -39
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +49 -32
- package/i18n/locales/en.json +580 -0
- package/i18n/locales/es.json +547 -0
- package/i18n/locales/fr.json +547 -0
- package/i18n/locales/pl.json +547 -0
- package/i18n/locales/uk.json +547 -0
- package/package.json +1 -1
|
@@ -11,6 +11,8 @@ const ui = useUiStore()
|
|
|
11
11
|
const models = useModelsStore()
|
|
12
12
|
const workspace = useWorkspaceStore()
|
|
13
13
|
|
|
14
|
+
const { t, n } = useI18n()
|
|
15
|
+
|
|
14
16
|
onMounted(() => models.ensureLoaded(workspace.workspaceId ?? undefined))
|
|
15
17
|
|
|
16
18
|
const block = computed<Block | undefined>(() =>
|
|
@@ -64,7 +66,7 @@ function openApprovalFor(approvalId: string) {
|
|
|
64
66
|
size="sm"
|
|
65
67
|
@click="close"
|
|
66
68
|
>
|
|
67
|
-
|
|
69
|
+
{{ t('focus.board') }}
|
|
68
70
|
</UButton>
|
|
69
71
|
<UIcon name="i-lucide-chevron-right" class="h-4 w-4 text-slate-600" />
|
|
70
72
|
<div
|
|
@@ -75,7 +77,9 @@ function openApprovalFor(approvalId: string) {
|
|
|
75
77
|
</div>
|
|
76
78
|
<div>
|
|
77
79
|
<h1 class="text-lg font-semibold text-white">{{ block.title }}</h1>
|
|
78
|
-
<div class="text-xs text-slate-500">
|
|
80
|
+
<div class="text-xs text-slate-500">
|
|
81
|
+
{{ t('focus.typeSubtitle', { type: typeMeta.label }) }}
|
|
82
|
+
</div>
|
|
79
83
|
</div>
|
|
80
84
|
<UBadge :color="statusMeta.chip as any" variant="subtle" class="ml-2">
|
|
81
85
|
{{ statusMeta.label }}
|
|
@@ -89,7 +93,7 @@ function openApprovalFor(approvalId: string) {
|
|
|
89
93
|
icon="i-lucide-play"
|
|
90
94
|
trailing-icon="i-lucide-chevron-down"
|
|
91
95
|
>
|
|
92
|
-
{{ instance ? '
|
|
96
|
+
{{ instance ? t('focus.rerunPipeline') : t('focus.runPipeline') }}
|
|
93
97
|
</UButton>
|
|
94
98
|
</UDropdownMenu>
|
|
95
99
|
<UButton icon="i-lucide-x" color="neutral" variant="ghost" @click="close" />
|
|
@@ -104,7 +108,7 @@ function openApprovalFor(approvalId: string) {
|
|
|
104
108
|
<div class="mb-4 flex items-center gap-2">
|
|
105
109
|
<UIcon name="i-lucide-workflow" class="h-4 w-4 text-slate-500" />
|
|
106
110
|
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-400">
|
|
107
|
-
{{ instance ? instance.pipelineName : '
|
|
111
|
+
{{ instance ? instance.pipelineName : t('focus.noPipelineRunning') }}
|
|
108
112
|
</h2>
|
|
109
113
|
</div>
|
|
110
114
|
|
|
@@ -119,7 +123,7 @@ function openApprovalFor(approvalId: string) {
|
|
|
119
123
|
v-else
|
|
120
124
|
class="flex flex-1 items-center justify-center rounded-xl border border-dashed border-slate-700 text-sm text-slate-500"
|
|
121
125
|
>
|
|
122
|
-
|
|
126
|
+
{{ t('focus.emptyPipelineHint') }}
|
|
123
127
|
</div>
|
|
124
128
|
</section>
|
|
125
129
|
|
|
@@ -129,29 +133,29 @@ function openApprovalFor(approvalId: string) {
|
|
|
129
133
|
>
|
|
130
134
|
<div>
|
|
131
135
|
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
132
|
-
|
|
136
|
+
{{ t('focus.description') }}
|
|
133
137
|
</div>
|
|
134
138
|
<p class="text-sm text-slate-300">{{ block.description }}</p>
|
|
135
139
|
</div>
|
|
136
140
|
<div v-if="instance">
|
|
137
141
|
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
138
|
-
|
|
142
|
+
{{ t('focus.overallProgress') }}
|
|
139
143
|
</div>
|
|
140
144
|
<UProgress :model-value="Math.round(block.progress * 100)" />
|
|
141
145
|
<div class="mt-1 text-[11px] text-slate-400">
|
|
142
|
-
{{
|
|
146
|
+
{{ n(block.progress, 'percent') }}
|
|
143
147
|
</div>
|
|
144
148
|
</div>
|
|
145
149
|
<div>
|
|
146
150
|
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
147
|
-
|
|
151
|
+
{{ t('focus.dependencies') }}
|
|
148
152
|
</div>
|
|
149
153
|
<div v-if="deps.length" class="flex flex-wrap gap-1">
|
|
150
154
|
<UBadge v-for="d in deps" :key="d.id" color="neutral" variant="subtle" size="sm">
|
|
151
155
|
{{ d.title }}
|
|
152
156
|
</UBadge>
|
|
153
157
|
</div>
|
|
154
|
-
<div v-else class="text-[11px] text-slate-500">
|
|
158
|
+
<div v-else class="text-[11px] text-slate-500">{{ t('focus.noDependencies') }}</div>
|
|
155
159
|
</div>
|
|
156
160
|
</aside>
|
|
157
161
|
</div>
|
|
@@ -17,6 +17,8 @@ const execution = useExecutionStore()
|
|
|
17
17
|
const board = useBoardStore()
|
|
18
18
|
const followUps = useFollowUpsStore()
|
|
19
19
|
|
|
20
|
+
const { t } = useI18n()
|
|
21
|
+
|
|
20
22
|
const { open, blockId, instanceId, stepIndex, close } = useResultView('follow-ups')
|
|
21
23
|
|
|
22
24
|
const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
|
|
@@ -57,15 +59,25 @@ async function onDismiss(item: FollowUpItem) {
|
|
|
57
59
|
if (id) await followUps.dismissItem(id, item.id).catch(() => {})
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
// Exhaustive map of the item status enum → label key (literal keys keep the typed-key
|
|
63
|
+
// drift guard live, vs a runtime-built `followUp.status.${status}`).
|
|
64
|
+
const STATUS_LABEL_KEYS: Record<FollowUpItem['status'], string> = {
|
|
65
|
+
pending: 'followUp.status.pending',
|
|
66
|
+
filed: 'followUp.status.filed',
|
|
67
|
+
queued: 'followUp.status.queued',
|
|
68
|
+
answered: 'followUp.status.answered',
|
|
69
|
+
dismissed: 'followUp.status.dismissed',
|
|
70
|
+
}
|
|
71
|
+
|
|
60
72
|
const STATUS_META: Record<
|
|
61
73
|
FollowUpItem['status'],
|
|
62
|
-
{
|
|
74
|
+
{ badge: 'neutral' | 'info' | 'success' | 'warning'; text: string }
|
|
63
75
|
> = {
|
|
64
|
-
pending: {
|
|
65
|
-
filed: {
|
|
66
|
-
queued: {
|
|
67
|
-
answered: {
|
|
68
|
-
dismissed: {
|
|
76
|
+
pending: { badge: 'warning', text: 'text-amber-300' },
|
|
77
|
+
filed: { badge: 'success', text: 'text-emerald-300' },
|
|
78
|
+
queued: { badge: 'info', text: 'text-sky-300' },
|
|
79
|
+
answered: { badge: 'info', text: 'text-sky-300' },
|
|
80
|
+
dismissed: { badge: 'neutral', text: 'text-slate-400' },
|
|
69
81
|
}
|
|
70
82
|
</script>
|
|
71
83
|
|
|
@@ -88,15 +100,20 @@ const STATUS_META: Record<
|
|
|
88
100
|
</span>
|
|
89
101
|
<div class="min-w-0 flex-1">
|
|
90
102
|
<h2 class="truncate text-sm font-semibold text-slate-100">
|
|
91
|
-
{{
|
|
103
|
+
{{
|
|
104
|
+
block ? t('followUp.titleWithBlock', { title: block.title }) : t('followUp.title')
|
|
105
|
+
}}
|
|
92
106
|
</h2>
|
|
93
107
|
<p class="truncate text-[11px] text-slate-400">
|
|
94
|
-
|
|
95
|
-
every item is decided.
|
|
108
|
+
{{ t('followUp.subtitle') }}
|
|
96
109
|
</p>
|
|
97
110
|
</div>
|
|
98
111
|
<UBadge :color="pendingCount > 0 ? 'warning' : 'success'" variant="subtle" size="sm">
|
|
99
|
-
{{
|
|
112
|
+
{{
|
|
113
|
+
pendingCount > 0
|
|
114
|
+
? t('followUp.badge.toDecide', { count: pendingCount }, pendingCount)
|
|
115
|
+
: t('followUp.badge.allDecided')
|
|
116
|
+
}}
|
|
100
117
|
</UBadge>
|
|
101
118
|
<button
|
|
102
119
|
class="rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
|
|
@@ -113,10 +130,9 @@ const STATUS_META: Record<
|
|
|
113
130
|
class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
|
|
114
131
|
>
|
|
115
132
|
<UIcon :name="FOLLOW_UP_COMPANION_META.icon" class="h-8 w-8 opacity-40" />
|
|
116
|
-
<p class="text-sm">
|
|
133
|
+
<p class="text-sm">{{ t('followUp.empty.title') }}</p>
|
|
117
134
|
<p class="max-w-sm text-[11px] text-slate-500">
|
|
118
|
-
|
|
119
|
-
live — you can act on them before the Coder even finishes.
|
|
135
|
+
{{ t('followUp.empty.hint') }}
|
|
120
136
|
</p>
|
|
121
137
|
</div>
|
|
122
138
|
|
|
@@ -146,14 +162,15 @@ const STATUS_META: Record<
|
|
|
146
162
|
{{ item.title }}
|
|
147
163
|
</h3>
|
|
148
164
|
<UBadge :color="STATUS_META[item.status].badge" variant="subtle" size="sm">
|
|
149
|
-
{{
|
|
165
|
+
{{ t(STATUS_LABEL_KEYS[item.status]) }}
|
|
150
166
|
</UBadge>
|
|
151
167
|
</div>
|
|
152
168
|
<p v-if="item.detail" class="mt-1 whitespace-pre-wrap text-[12px] text-slate-300">
|
|
153
169
|
{{ item.detail }}
|
|
154
170
|
</p>
|
|
155
171
|
<p v-if="item.suggestedAction" class="mt-1 text-[11px] text-slate-400">
|
|
156
|
-
<span class="text-slate-500">
|
|
172
|
+
<span class="text-slate-500">{{ t('followUp.suggested') }}</span>
|
|
173
|
+
{{ item.suggestedAction }}
|
|
157
174
|
</p>
|
|
158
175
|
<p v-if="item.status === 'filed' && item.ticketUrl" class="mt-1 text-[11px]">
|
|
159
176
|
<a
|
|
@@ -162,14 +179,15 @@ const STATUS_META: Record<
|
|
|
162
179
|
rel="noopener"
|
|
163
180
|
class="text-emerald-300 hover:underline"
|
|
164
181
|
>
|
|
165
|
-
{{ item.ticketExternalId ?? '
|
|
182
|
+
{{ item.ticketExternalId ?? t('followUp.viewIssue') }}
|
|
166
183
|
</a>
|
|
167
184
|
</p>
|
|
168
185
|
<p
|
|
169
186
|
v-if="item.status === 'answered' && item.answer"
|
|
170
187
|
class="mt-1 text-[11px] text-slate-300"
|
|
171
188
|
>
|
|
172
|
-
<span class="text-slate-500">
|
|
189
|
+
<span class="text-slate-500">{{ t('followUp.yourAnswer') }}</span>
|
|
190
|
+
{{ item.answer }}
|
|
173
191
|
</p>
|
|
174
192
|
|
|
175
193
|
<!-- Actions (only while the item is still undecided) -->
|
|
@@ -179,7 +197,7 @@ const STATUS_META: Record<
|
|
|
179
197
|
<textarea
|
|
180
198
|
v-model="drafts[item.id]"
|
|
181
199
|
rows="2"
|
|
182
|
-
placeholder="
|
|
200
|
+
:placeholder="t('followUp.answerPlaceholder')"
|
|
183
201
|
class="w-full resize-y rounded-md border border-slate-700 bg-slate-950/60 px-2.5 py-1.5 text-[12px] text-slate-100 placeholder:text-slate-600 focus:border-sky-500 focus:outline-none"
|
|
184
202
|
/>
|
|
185
203
|
<div class="flex items-center gap-2">
|
|
@@ -190,7 +208,7 @@ const STATUS_META: Record<
|
|
|
190
208
|
:disabled="!(drafts[item.id] ?? '').trim()"
|
|
191
209
|
@click="onAnswer(item)"
|
|
192
210
|
>
|
|
193
|
-
|
|
211
|
+
{{ t('followUp.actions.answerAndSend') }}
|
|
194
212
|
</UButton>
|
|
195
213
|
<UButton
|
|
196
214
|
size="xs"
|
|
@@ -199,7 +217,7 @@ const STATUS_META: Record<
|
|
|
199
217
|
:loading="followUps.isActing(item.id)"
|
|
200
218
|
@click="onDismiss(item)"
|
|
201
219
|
>
|
|
202
|
-
|
|
220
|
+
{{ t('followUp.actions.dismiss') }}
|
|
203
221
|
</UButton>
|
|
204
222
|
</div>
|
|
205
223
|
</div>
|
|
@@ -213,7 +231,7 @@ const STATUS_META: Record<
|
|
|
213
231
|
:loading="followUps.isActing(item.id)"
|
|
214
232
|
@click="onFile(item)"
|
|
215
233
|
>
|
|
216
|
-
|
|
234
|
+
{{ t('followUp.actions.fileAsIssue') }}
|
|
217
235
|
</UButton>
|
|
218
236
|
<UButton
|
|
219
237
|
size="xs"
|
|
@@ -223,7 +241,7 @@ const STATUS_META: Record<
|
|
|
223
241
|
:loading="followUps.isActing(item.id)"
|
|
224
242
|
@click="onQueue(item)"
|
|
225
243
|
>
|
|
226
|
-
|
|
244
|
+
{{ t('followUp.actions.sendToCoder') }}
|
|
227
245
|
</UButton>
|
|
228
246
|
<UButton
|
|
229
247
|
size="xs"
|
|
@@ -232,7 +250,7 @@ const STATUS_META: Record<
|
|
|
232
250
|
:loading="followUps.isActing(item.id)"
|
|
233
251
|
@click="onDismiss(item)"
|
|
234
252
|
>
|
|
235
|
-
|
|
253
|
+
{{ t('followUp.actions.dismiss') }}
|
|
236
254
|
</UButton>
|
|
237
255
|
</div>
|
|
238
256
|
</div>
|
|
@@ -246,10 +264,17 @@ const STATUS_META: Record<
|
|
|
246
264
|
class="flex items-center justify-between border-t border-slate-800 px-5 py-2.5 text-[11px] text-slate-400"
|
|
247
265
|
>
|
|
248
266
|
<span>
|
|
249
|
-
{{
|
|
250
|
-
|
|
267
|
+
{{
|
|
268
|
+
t(
|
|
269
|
+
'followUp.footer.summary',
|
|
270
|
+
{ count: items.length, undecided: pendingCount },
|
|
271
|
+
items.length,
|
|
272
|
+
)
|
|
273
|
+
}}
|
|
251
274
|
</span>
|
|
252
|
-
<span v-if="maxLoops > 0">
|
|
275
|
+
<span v-if="maxLoops > 0">{{
|
|
276
|
+
t('followUp.footer.loops', { loops, max: maxLoops })
|
|
277
|
+
}}</span>
|
|
253
278
|
</footer>
|
|
254
279
|
</div>
|
|
255
280
|
</div>
|
|
@@ -5,12 +5,20 @@
|
|
|
5
5
|
// stream), surfaces the ephemeral environment URL, and drives the human actions: confirm
|
|
6
6
|
// (pass + tear down + advance), request a fix from findings (the Tester's fixer), pull latest
|
|
7
7
|
// main into the branch + redeploy (conflict → conflict-resolver), recreate, or destroy the env.
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
HumanTestEnvironmentStatus,
|
|
10
|
+
HumanTestRound,
|
|
11
|
+
HumanTestStepState,
|
|
12
|
+
} from '~/types/execution'
|
|
13
|
+
|
|
14
|
+
// The round outcome union (contracts state it inline, so derive it off the round type).
|
|
15
|
+
type HumanTestRoundOutcome = NonNullable<HumanTestRound['outcome']>
|
|
9
16
|
import StepRunMeta from '~/components/panels/StepRunMeta.vue'
|
|
10
17
|
|
|
11
18
|
const board = useBoardStore()
|
|
12
19
|
const execution = useExecutionStore()
|
|
13
20
|
const humanTest = useHumanTestStore()
|
|
21
|
+
const { t, d } = useI18n()
|
|
14
22
|
|
|
15
23
|
// Shared seam contract (open/blockId/close + Escape). No `onOpen` loader: the gate state
|
|
16
24
|
// rides on the execution step, pushed over the stream.
|
|
@@ -38,21 +46,36 @@ const working = computed(
|
|
|
38
46
|
phase.value === 'resolving_conflicts',
|
|
39
47
|
)
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
// Exhaustive enum→label maps of literal keys (keeps the typed-key drift guard live vs a
|
|
50
|
+
// runtime-built `humanTest.envStatus.${status}`). The colour stays a component constant.
|
|
51
|
+
const ENV_STATUS_LABEL: Record<HumanTestEnvironmentStatus, string> = {
|
|
52
|
+
provisioning: 'humanTest.envStatus.provisioning',
|
|
53
|
+
ready: 'humanTest.envStatus.ready',
|
|
54
|
+
failed: 'humanTest.envStatus.failed',
|
|
55
|
+
expired: 'humanTest.envStatus.expired',
|
|
56
|
+
tearing_down: 'humanTest.envStatus.tearingDown',
|
|
57
|
+
torn_down: 'humanTest.envStatus.tornDown',
|
|
58
|
+
}
|
|
59
|
+
const ENV_STATUS_COLOR: Record<HumanTestEnvironmentStatus, string> = {
|
|
60
|
+
provisioning: 'text-amber-300',
|
|
61
|
+
ready: 'text-emerald-300',
|
|
62
|
+
failed: 'text-rose-300',
|
|
63
|
+
expired: 'text-slate-400',
|
|
64
|
+
tearing_down: 'text-slate-400',
|
|
65
|
+
torn_down: 'text-slate-400',
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
const PHASE_LABEL: Record<NonNullable<HumanTestStepState['phase']>, string> = {
|
|
51
|
-
provisioning: '
|
|
52
|
-
awaiting_human: '
|
|
53
|
-
fixing: '
|
|
54
|
-
resolving_conflicts: '
|
|
55
|
-
passed: '
|
|
69
|
+
provisioning: 'humanTest.phase.provisioning',
|
|
70
|
+
awaiting_human: 'humanTest.phase.awaitingHuman',
|
|
71
|
+
fixing: 'humanTest.phase.fixing',
|
|
72
|
+
resolving_conflicts: 'humanTest.phase.resolvingConflicts',
|
|
73
|
+
passed: 'humanTest.phase.passed',
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const ROUND_OUTCOME_LABEL: Record<HumanTestRoundOutcome, string> = {
|
|
77
|
+
completed: 'humanTest.roundOutcome.completed',
|
|
78
|
+
failed: 'humanTest.roundOutcome.failed',
|
|
56
79
|
}
|
|
57
80
|
|
|
58
81
|
const findings = ref('')
|
|
@@ -115,10 +138,12 @@ const canDestroy = computed(
|
|
|
115
138
|
</span>
|
|
116
139
|
<div class="min-w-0 flex-1">
|
|
117
140
|
<h2 class="truncate text-sm font-semibold text-slate-100">
|
|
118
|
-
|
|
141
|
+
{{
|
|
142
|
+
block ? t('humanTest.titleWithBlock', { title: block.title }) : t('humanTest.title')
|
|
143
|
+
}}
|
|
119
144
|
</h2>
|
|
120
145
|
<p class="truncate text-[11px] text-slate-400">
|
|
121
|
-
{{ phase ? PHASE_LABEL[phase] : '
|
|
146
|
+
{{ phase ? t(PHASE_LABEL[phase]) : t('humanTest.subtitle') }}
|
|
122
147
|
</p>
|
|
123
148
|
</div>
|
|
124
149
|
<button
|
|
@@ -135,24 +160,24 @@ const canDestroy = computed(
|
|
|
135
160
|
class="flex flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
|
|
136
161
|
>
|
|
137
162
|
<UIcon name="i-lucide-user-check" class="h-8 w-8 opacity-40" />
|
|
138
|
-
<p class="text-sm">
|
|
163
|
+
<p class="text-sm">{{ t('humanTest.notStarted') }}</p>
|
|
139
164
|
</div>
|
|
140
165
|
|
|
141
166
|
<template v-else>
|
|
142
167
|
<!-- Environment -->
|
|
143
168
|
<section class="rounded-lg border border-slate-800 bg-slate-900/60 p-4">
|
|
144
169
|
<h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
145
|
-
|
|
170
|
+
{{ t('humanTest.environment.heading') }}
|
|
146
171
|
</h3>
|
|
147
172
|
<div v-if="env" class="space-y-2">
|
|
148
173
|
<div class="flex items-center gap-2 text-[13px]">
|
|
149
174
|
<UIcon
|
|
150
175
|
name="i-lucide-circle-dot"
|
|
151
176
|
class="h-3.5 w-3.5"
|
|
152
|
-
:class="
|
|
177
|
+
:class="ENV_STATUS_COLOR[env.status]"
|
|
153
178
|
/>
|
|
154
|
-
<span :class="
|
|
155
|
-
|
|
179
|
+
<span :class="ENV_STATUS_COLOR[env.status]">{{
|
|
180
|
+
t(ENV_STATUS_LABEL[env.status])
|
|
156
181
|
}}</span>
|
|
157
182
|
</div>
|
|
158
183
|
<a
|
|
@@ -165,13 +190,17 @@ const canDestroy = computed(
|
|
|
165
190
|
<UIcon name="i-lucide-external-link" class="h-3.5 w-3.5 shrink-0" />
|
|
166
191
|
{{ env.url }}
|
|
167
192
|
</a>
|
|
168
|
-
<p v-else class="text-[12px] italic text-slate-500">
|
|
193
|
+
<p v-else class="text-[12px] italic text-slate-500">
|
|
194
|
+
{{ t('humanTest.environment.noUrl') }}
|
|
195
|
+
</p>
|
|
169
196
|
<p v-if="env.expiresAt" class="text-[11px] text-slate-500">
|
|
170
|
-
|
|
197
|
+
{{
|
|
198
|
+
t('humanTest.environment.expires', { date: d(new Date(env.expiresAt), 'long') })
|
|
199
|
+
}}
|
|
171
200
|
</p>
|
|
172
201
|
</div>
|
|
173
202
|
<p v-else class="text-[12px] text-amber-300/90">
|
|
174
|
-
{{ ht.degradedReason ?? '
|
|
203
|
+
{{ ht.degradedReason ?? t('humanTest.environment.none') }}
|
|
175
204
|
</p>
|
|
176
205
|
<p v-if="env && ht.degradedReason" class="mt-2 text-[12px] text-amber-300/90">
|
|
177
206
|
{{ ht.degradedReason }}
|
|
@@ -188,7 +217,7 @@ const canDestroy = computed(
|
|
|
188
217
|
:disabled="busy || !canManageEnv"
|
|
189
218
|
@click="recreate"
|
|
190
219
|
>
|
|
191
|
-
|
|
220
|
+
{{ t('humanTest.actions.recreate') }}
|
|
192
221
|
</UButton>
|
|
193
222
|
<UButton
|
|
194
223
|
size="xs"
|
|
@@ -198,7 +227,7 @@ const canDestroy = computed(
|
|
|
198
227
|
:disabled="busy || !canDestroy"
|
|
199
228
|
@click="destroy"
|
|
200
229
|
>
|
|
201
|
-
|
|
230
|
+
{{ t('humanTest.actions.destroy') }}
|
|
202
231
|
</UButton>
|
|
203
232
|
<UButton
|
|
204
233
|
size="xs"
|
|
@@ -209,7 +238,7 @@ const canDestroy = computed(
|
|
|
209
238
|
:disabled="busy || !canManageEnv"
|
|
210
239
|
@click="pullMain"
|
|
211
240
|
>
|
|
212
|
-
|
|
241
|
+
{{ t('humanTest.actions.pullMain') }}
|
|
213
242
|
</UButton>
|
|
214
243
|
</div>
|
|
215
244
|
</section>
|
|
@@ -220,7 +249,7 @@ const canDestroy = computed(
|
|
|
220
249
|
class="flex items-center gap-2 rounded-lg border border-slate-800 bg-slate-950/40 px-3 py-2 text-[12px] text-slate-300"
|
|
221
250
|
>
|
|
222
251
|
<UIcon name="i-lucide-loader" class="h-3.5 w-3.5 animate-spin text-amber-300" />
|
|
223
|
-
{{ phase ? PHASE_LABEL[phase] : '' }}
|
|
252
|
+
{{ phase ? t(PHASE_LABEL[phase]) : '' }}
|
|
224
253
|
</p>
|
|
225
254
|
|
|
226
255
|
<!-- Findings / fix -->
|
|
@@ -230,20 +259,20 @@ const canDestroy = computed(
|
|
|
230
259
|
>
|
|
231
260
|
<div class="flex items-center justify-between">
|
|
232
261
|
<h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
233
|
-
|
|
262
|
+
{{ t('humanTest.fix.heading') }}
|
|
234
263
|
</h3>
|
|
235
264
|
<button
|
|
236
265
|
class="text-[12px] text-slate-400 hover:text-slate-200"
|
|
237
266
|
@click="showFindings = !showFindings"
|
|
238
267
|
>
|
|
239
|
-
{{ showFindings ? '
|
|
268
|
+
{{ showFindings ? t('humanTest.fix.cancel') : t('humanTest.fix.requestFix') }}
|
|
240
269
|
</button>
|
|
241
270
|
</div>
|
|
242
271
|
<div v-if="showFindings" class="mt-2 space-y-2">
|
|
243
272
|
<textarea
|
|
244
273
|
v-model="findings"
|
|
245
274
|
rows="4"
|
|
246
|
-
placeholder="
|
|
275
|
+
:placeholder="t('humanTest.fix.placeholder')"
|
|
247
276
|
class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none"
|
|
248
277
|
/>
|
|
249
278
|
<UButton
|
|
@@ -254,7 +283,7 @@ const canDestroy = computed(
|
|
|
254
283
|
:disabled="busy || !findings.trim()"
|
|
255
284
|
@click="submitFix"
|
|
256
285
|
>
|
|
257
|
-
|
|
286
|
+
{{ t('humanTest.fix.send') }}
|
|
258
287
|
</UButton>
|
|
259
288
|
</div>
|
|
260
289
|
</section>
|
|
@@ -265,7 +294,7 @@ const canDestroy = computed(
|
|
|
265
294
|
class="rounded-lg border border-slate-800 bg-slate-900/60 p-4"
|
|
266
295
|
>
|
|
267
296
|
<h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
268
|
-
|
|
297
|
+
{{ t('humanTest.history.heading', { count: ht.attempts }, ht.attempts) }}
|
|
269
298
|
</h3>
|
|
270
299
|
<ol class="space-y-2">
|
|
271
300
|
<li v-for="(r, i) in ht.rounds" :key="i" class="flex items-start gap-2 text-[12px]">
|
|
@@ -275,7 +304,9 @@ const canDestroy = computed(
|
|
|
275
304
|
/>
|
|
276
305
|
<div class="min-w-0 flex-1">
|
|
277
306
|
<span class="text-slate-200">{{
|
|
278
|
-
r.kind === 'fix'
|
|
307
|
+
r.kind === 'fix'
|
|
308
|
+
? t('humanTest.history.fixRequested')
|
|
309
|
+
: t('humanTest.history.pulledMain')
|
|
279
310
|
}}</span>
|
|
280
311
|
<span
|
|
281
312
|
class="ml-1.5 rounded px-1 text-[10px] uppercase"
|
|
@@ -287,7 +318,11 @@ const canDestroy = computed(
|
|
|
287
318
|
: 'bg-slate-500/15 text-slate-300'
|
|
288
319
|
"
|
|
289
320
|
>
|
|
290
|
-
{{
|
|
321
|
+
{{
|
|
322
|
+
r.outcome
|
|
323
|
+
? t(ROUND_OUTCOME_LABEL[r.outcome])
|
|
324
|
+
: t('humanTest.roundOutcome.inProgress')
|
|
325
|
+
}}
|
|
291
326
|
</span>
|
|
292
327
|
<p v-if="r.findings" class="leading-snug text-slate-400">{{ r.findings }}</p>
|
|
293
328
|
</div>
|
|
@@ -318,7 +353,7 @@ const canDestroy = computed(
|
|
|
318
353
|
:disabled="busy || !awaitingHuman"
|
|
319
354
|
@click="confirm"
|
|
320
355
|
>
|
|
321
|
-
|
|
356
|
+
{{ t('humanTest.confirm') }}
|
|
322
357
|
</UButton>
|
|
323
358
|
</footer>
|
|
324
359
|
</div>
|