@cat-factory/app 0.47.8 → 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 +97 -59
- package/app/components/clarity/ClarityReviewWindow.vue +85 -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/panels/AgentStepDetail.vue +1 -0
- package/app/components/panels/InspectorPanel.vue +1 -0
- package/app/components/requirements/RequirementsReviewWindow.vue +131 -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/app/pages/index.vue +1 -0
- 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
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
import type {
|
|
9
9
|
RequirementGroup,
|
|
10
10
|
RequirementItem,
|
|
11
|
+
RequirementKind,
|
|
11
12
|
RequirementPriority,
|
|
12
13
|
SpecModule,
|
|
13
14
|
} from '~/types/spec'
|
|
14
15
|
|
|
16
|
+
const { t } = useI18n()
|
|
15
17
|
const board = useBoardStore()
|
|
16
18
|
const serviceSpec = useServiceSpecStore()
|
|
17
19
|
|
|
@@ -86,10 +88,19 @@ function selectGroup(m: number, g: number) {
|
|
|
86
88
|
selected.value = { m, g }
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
// Exhaustive priority → label/chip map. Literal `t()` keys keep the typed-key drift
|
|
92
|
+
// guard live, vs a runtime-built `spec.priority.${value}`.
|
|
89
93
|
const PRIORITY_META: Record<RequirementPriority, { label: string; chip: string }> = {
|
|
90
|
-
must: { label: '
|
|
91
|
-
should: { label: '
|
|
92
|
-
could: { label: '
|
|
94
|
+
must: { label: t('spec.priority.must'), chip: 'error' },
|
|
95
|
+
should: { label: t('spec.priority.should'), chip: 'warning' },
|
|
96
|
+
could: { label: t('spec.priority.could'), chip: 'neutral' },
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Exhaustive requirement-kind → label map (closed union from the contracts).
|
|
100
|
+
const KIND_LABELS: Record<RequirementKind, string> = {
|
|
101
|
+
functional: t('spec.kind.functional'),
|
|
102
|
+
nonfunctional: t('spec.kind.nonfunctional'),
|
|
103
|
+
constraint: t('spec.kind.constraint'),
|
|
93
104
|
}
|
|
94
105
|
|
|
95
106
|
function reqCount(group: RequirementGroup): number {
|
|
@@ -98,6 +109,9 @@ function reqCount(group: RequirementGroup): number {
|
|
|
98
109
|
function priorityMeta(item: RequirementItem) {
|
|
99
110
|
return PRIORITY_META[item.priority] ?? PRIORITY_META.could
|
|
100
111
|
}
|
|
112
|
+
function kindLabel(item: RequirementItem): string {
|
|
113
|
+
return KIND_LABELS[item.kind] ?? item.kind
|
|
114
|
+
}
|
|
101
115
|
</script>
|
|
102
116
|
|
|
103
117
|
<template>
|
|
@@ -118,7 +132,7 @@ function priorityMeta(item: RequirementItem) {
|
|
|
118
132
|
<UIcon name="i-lucide-scroll-text" class="h-5 w-5 text-indigo-300" />
|
|
119
133
|
</div>
|
|
120
134
|
<div class="min-w-0">
|
|
121
|
-
<h1 class="truncate text-base font-semibold text-white">
|
|
135
|
+
<h1 class="truncate text-base font-semibold text-white">{{ t('spec.title') }}</h1>
|
|
122
136
|
<p v-if="block" class="truncate text-xs text-slate-500">
|
|
123
137
|
{{ spec?.service || block.title }}
|
|
124
138
|
</p>
|
|
@@ -133,7 +147,7 @@ function priorityMeta(item: RequirementItem) {
|
|
|
133
147
|
icon="i-lucide-list-tree"
|
|
134
148
|
@click="mode = 'structured'"
|
|
135
149
|
>
|
|
136
|
-
|
|
150
|
+
{{ t('spec.mode.structured') }}
|
|
137
151
|
</UButton>
|
|
138
152
|
<UButton
|
|
139
153
|
:color="mode === 'gherkin' ? 'primary' : 'neutral'"
|
|
@@ -141,10 +155,10 @@ function priorityMeta(item: RequirementItem) {
|
|
|
141
155
|
size="xs"
|
|
142
156
|
icon="i-lucide-square-check-big"
|
|
143
157
|
:disabled="!hasGherkin"
|
|
144
|
-
:title="hasGherkin ? '
|
|
158
|
+
:title="hasGherkin ? t('spec.mode.gherkinTooltip') : t('spec.mode.gherkinNone')"
|
|
145
159
|
@click="mode = 'gherkin'"
|
|
146
160
|
>
|
|
147
|
-
|
|
161
|
+
{{ t('spec.mode.gherkin') }}
|
|
148
162
|
</UButton>
|
|
149
163
|
</div>
|
|
150
164
|
<UButton icon="i-lucide-x" color="neutral" variant="ghost" size="sm" @click="close" />
|
|
@@ -157,7 +171,7 @@ function priorityMeta(item: RequirementItem) {
|
|
|
157
171
|
class="flex flex-1 items-center justify-center gap-2 text-sm text-slate-400"
|
|
158
172
|
>
|
|
159
173
|
<UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
|
|
160
|
-
|
|
174
|
+
{{ t('spec.loading') }}
|
|
161
175
|
</div>
|
|
162
176
|
|
|
163
177
|
<!-- error -->
|
|
@@ -166,7 +180,7 @@ function priorityMeta(item: RequirementItem) {
|
|
|
166
180
|
class="flex flex-1 flex-col items-center justify-center gap-2 p-8 text-center text-sm text-slate-400"
|
|
167
181
|
>
|
|
168
182
|
<UIcon name="i-lucide-triangle-alert" class="h-6 w-6 text-amber-400" />
|
|
169
|
-
|
|
183
|
+
{{ t('spec.error') }}
|
|
170
184
|
</div>
|
|
171
185
|
|
|
172
186
|
<!-- empty: no spec on the repo's default branch yet -->
|
|
@@ -176,11 +190,9 @@ function priorityMeta(item: RequirementItem) {
|
|
|
176
190
|
>
|
|
177
191
|
<UIcon name="i-lucide-scroll-text" class="h-8 w-8 text-slate-600" />
|
|
178
192
|
<div>
|
|
179
|
-
<p class="text-sm font-medium text-slate-300">
|
|
193
|
+
<p class="text-sm font-medium text-slate-300">{{ t('spec.empty.title') }}</p>
|
|
180
194
|
<p class="mx-auto mt-1 max-w-md text-xs text-slate-500">
|
|
181
|
-
|
|
182
|
-
run their pipelines; once it lands on main it will appear here, with its Gherkin
|
|
183
|
-
scenarios.
|
|
195
|
+
{{ t('spec.empty.description') }}
|
|
184
196
|
</p>
|
|
185
197
|
</div>
|
|
186
198
|
</div>
|
|
@@ -198,7 +210,7 @@ function priorityMeta(item: RequirementItem) {
|
|
|
198
210
|
icon="i-lucide-info"
|
|
199
211
|
@click="selected = null"
|
|
200
212
|
>
|
|
201
|
-
|
|
213
|
+
{{ t('spec.overview') }}
|
|
202
214
|
</UButton>
|
|
203
215
|
<div v-for="(mod, mi) in modules" :key="mi" class="mb-3">
|
|
204
216
|
<div
|
|
@@ -226,7 +238,7 @@ function priorityMeta(item: RequirementItem) {
|
|
|
226
238
|
v-if="(mod.groups?.length ?? 0) === 0"
|
|
227
239
|
class="px-2 py-1 text-[11px] italic text-slate-600"
|
|
228
240
|
>
|
|
229
|
-
|
|
241
|
+
{{ t('spec.noFeatureGroups') }}
|
|
230
242
|
</li>
|
|
231
243
|
</ul>
|
|
232
244
|
</div>
|
|
@@ -240,10 +252,13 @@ function priorityMeta(item: RequirementItem) {
|
|
|
240
252
|
<p v-if="spec?.summary" class="mt-2 whitespace-pre-line text-sm text-slate-300">
|
|
241
253
|
{{ spec.summary }}
|
|
242
254
|
</p>
|
|
243
|
-
<p v-else class="mt-2 text-sm text-slate-500">
|
|
255
|
+
<p v-else class="mt-2 text-sm text-slate-500">{{ t('spec.noSummary') }}</p>
|
|
244
256
|
<p class="mt-4 text-xs text-slate-500">
|
|
245
|
-
{{
|
|
246
|
-
|
|
257
|
+
{{
|
|
258
|
+
hasGherkin
|
|
259
|
+
? t('spec.moduleHintGherkin', { count: modules.length }, modules.length)
|
|
260
|
+
: t('spec.moduleHint', { count: modules.length }, modules.length)
|
|
261
|
+
}}
|
|
247
262
|
</p>
|
|
248
263
|
</template>
|
|
249
264
|
|
|
@@ -267,14 +282,14 @@ function priorityMeta(item: RequirementItem) {
|
|
|
267
282
|
v-else
|
|
268
283
|
class="mt-4 rounded-lg border border-dashed border-slate-700 p-6 text-center text-sm text-slate-500"
|
|
269
284
|
>
|
|
270
|
-
|
|
285
|
+
{{ t('spec.noGherkinForGroup') }}
|
|
271
286
|
</div>
|
|
272
287
|
</template>
|
|
273
288
|
|
|
274
289
|
<!-- STRUCTURED view: requirements + acceptance + domain rules -->
|
|
275
290
|
<template v-else>
|
|
276
291
|
<div v-if="reqCount(selectedGroup) === 0" class="mt-4 text-sm text-slate-500">
|
|
277
|
-
|
|
292
|
+
{{ t('spec.noRequirements') }}
|
|
278
293
|
</div>
|
|
279
294
|
<ul class="mt-4 space-y-4">
|
|
280
295
|
<li
|
|
@@ -288,7 +303,9 @@ function priorityMeta(item: RequirementItem) {
|
|
|
288
303
|
<UBadge :color="priorityMeta(req).chip as any" variant="subtle" size="sm">
|
|
289
304
|
{{ priorityMeta(req).label }}
|
|
290
305
|
</UBadge>
|
|
291
|
-
<UBadge color="neutral" variant="subtle" size="sm">{{
|
|
306
|
+
<UBadge color="neutral" variant="subtle" size="sm">{{
|
|
307
|
+
kindLabel(req)
|
|
308
|
+
}}</UBadge>
|
|
292
309
|
</div>
|
|
293
310
|
</div>
|
|
294
311
|
<p
|
|
@@ -304,13 +321,22 @@ function priorityMeta(item: RequirementItem) {
|
|
|
304
321
|
class="rounded-md border border-slate-800 bg-slate-950/50 px-3 py-2 text-[12.5px] leading-relaxed"
|
|
305
322
|
>
|
|
306
323
|
<p class="text-slate-300">
|
|
307
|
-
<span class="font-semibold text-emerald-400">
|
|
324
|
+
<span class="font-semibold text-emerald-400">{{
|
|
325
|
+
t('spec.acceptance.given')
|
|
326
|
+
}}</span>
|
|
327
|
+
{{ ac.given }}
|
|
308
328
|
</p>
|
|
309
329
|
<p class="text-slate-300">
|
|
310
|
-
<span class="font-semibold text-sky-400">
|
|
330
|
+
<span class="font-semibold text-sky-400">{{
|
|
331
|
+
t('spec.acceptance.when')
|
|
332
|
+
}}</span>
|
|
333
|
+
{{ ac.when }}
|
|
311
334
|
</p>
|
|
312
335
|
<p class="text-slate-300">
|
|
313
|
-
<span class="font-semibold text-violet-400">
|
|
336
|
+
<span class="font-semibold text-violet-400">{{
|
|
337
|
+
t('spec.acceptance.then')
|
|
338
|
+
}}</span>
|
|
339
|
+
{{ ac.outcome }}
|
|
314
340
|
</p>
|
|
315
341
|
</div>
|
|
316
342
|
</div>
|
|
@@ -323,7 +349,7 @@ function priorityMeta(item: RequirementItem) {
|
|
|
323
349
|
class="mb-2 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-400"
|
|
324
350
|
>
|
|
325
351
|
<UIcon name="i-lucide-shield-check" class="h-3.5 w-3.5" />
|
|
326
|
-
|
|
352
|
+
{{ t('spec.domainRules') }}
|
|
327
353
|
</div>
|
|
328
354
|
<ul class="space-y-1.5">
|
|
329
355
|
<li
|
|
@@ -332,9 +358,9 @@ function priorityMeta(item: RequirementItem) {
|
|
|
332
358
|
class="rounded-md border border-slate-800 bg-slate-900/60 px-3 py-2 text-[13px] text-slate-300"
|
|
333
359
|
>
|
|
334
360
|
{{ rule.rule }}
|
|
335
|
-
<span v-if="rule.rationale" class="text-slate-500">
|
|
336
|
-
|
|
337
|
-
>
|
|
361
|
+
<span v-if="rule.rationale" class="text-slate-500">{{
|
|
362
|
+
t('spec.ruleRationale', { rationale: rule.rationale })
|
|
363
|
+
}}</span>
|
|
338
364
|
</li>
|
|
339
365
|
</ul>
|
|
340
366
|
</div>
|
|
@@ -20,6 +20,7 @@ import StepRunMeta from '~/components/panels/StepRunMeta.vue'
|
|
|
20
20
|
|
|
21
21
|
const board = useBoardStore()
|
|
22
22
|
const execution = useExecutionStore()
|
|
23
|
+
const { t } = useI18n()
|
|
23
24
|
|
|
24
25
|
// Per-window blob cache for the captured screenshots; revoked on unmount.
|
|
25
26
|
const blobs = useArtifactBlobs()
|
|
@@ -52,11 +53,30 @@ watch(
|
|
|
52
53
|
{ immediate: true },
|
|
53
54
|
)
|
|
54
55
|
|
|
55
|
-
const STATUS_META
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
const STATUS_META = computed<
|
|
57
|
+
Record<TestOutcome['status'], { icon: string; text: string; label: string }>
|
|
58
|
+
>(() => ({
|
|
59
|
+
passed: {
|
|
60
|
+
icon: 'i-lucide-circle-check',
|
|
61
|
+
text: 'text-emerald-400',
|
|
62
|
+
label: t('testing.status.passed'),
|
|
63
|
+
},
|
|
64
|
+
failed: { icon: 'i-lucide-circle-x', text: 'text-rose-400', label: t('testing.status.failed') },
|
|
65
|
+
skipped: {
|
|
66
|
+
icon: 'i-lucide-circle-minus',
|
|
67
|
+
text: 'text-slate-500',
|
|
68
|
+
label: t('testing.status.skipped'),
|
|
69
|
+
},
|
|
70
|
+
}))
|
|
71
|
+
|
|
72
|
+
// Exhaustive enum→key map for the concern severity chip label (literal keys keep the
|
|
73
|
+
// typed-key drift guard live).
|
|
74
|
+
const SEVERITY_LABELS = computed<Record<TestConcern['severity'], string>>(() => ({
|
|
75
|
+
critical: t('testing.severity.critical'),
|
|
76
|
+
high: t('testing.severity.high'),
|
|
77
|
+
medium: t('testing.severity.medium'),
|
|
78
|
+
low: t('testing.severity.low'),
|
|
79
|
+
}))
|
|
60
80
|
|
|
61
81
|
const SEVERITY_META: Record<TestConcern['severity'], { text: string; chip: string; rank: number }> =
|
|
62
82
|
{
|
|
@@ -150,7 +170,7 @@ const scenarioLayout = computed<{ groups: ScenarioGroup[]; ungrouped: TestScreen
|
|
|
150
170
|
if (leftoverOutcomes.length || leftoverConcerns.length) {
|
|
151
171
|
out.push({
|
|
152
172
|
key: 'other',
|
|
153
|
-
title: r.tested.length ? '
|
|
173
|
+
title: r.tested.length ? t('testing.otherChecks') : t('testing.checks'),
|
|
154
174
|
other: true,
|
|
155
175
|
outcomes: leftoverOutcomes,
|
|
156
176
|
concerns: leftoverConcerns,
|
|
@@ -169,7 +189,7 @@ const lightboxItems = computed(() =>
|
|
|
169
189
|
screenshots.value.map((s) => ({
|
|
170
190
|
artifactId: s.artifactId,
|
|
171
191
|
label: s.view,
|
|
172
|
-
alt:
|
|
192
|
+
alt: t('testing.screenshotAlt', { view: s.view }),
|
|
173
193
|
})),
|
|
174
194
|
)
|
|
175
195
|
const lightboxOpen = ref(false)
|
|
@@ -238,7 +258,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
238
258
|
tabindex="-1"
|
|
239
259
|
role="dialog"
|
|
240
260
|
aria-modal="true"
|
|
241
|
-
aria-label="
|
|
261
|
+
:aria-label="t('testing.title')"
|
|
242
262
|
class="m-4 flex w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl focus:outline-none"
|
|
243
263
|
>
|
|
244
264
|
<!-- Header -->
|
|
@@ -250,10 +270,10 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
250
270
|
</span>
|
|
251
271
|
<div class="min-w-0 flex-1">
|
|
252
272
|
<h2 class="truncate text-sm font-semibold text-slate-100">
|
|
253
|
-
|
|
273
|
+
{{ t('testing.title') }}{{ block ? ` — ${block.title}` : '' }}
|
|
254
274
|
</h2>
|
|
255
275
|
<p class="truncate text-[11px] text-slate-400">
|
|
256
|
-
|
|
276
|
+
{{ t('testing.subtitle') }}
|
|
257
277
|
</p>
|
|
258
278
|
</div>
|
|
259
279
|
<UBadge
|
|
@@ -262,15 +282,19 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
262
282
|
variant="subtle"
|
|
263
283
|
size="sm"
|
|
264
284
|
>
|
|
265
|
-
{{ report.greenlight ? '
|
|
285
|
+
{{ report.greenlight ? t('testing.badge.greenlit') : t('testing.badge.needsFixes') }}
|
|
266
286
|
</UBadge>
|
|
267
287
|
<span
|
|
268
288
|
v-if="testState && testState.attempts > 0"
|
|
269
289
|
class="text-[11px] text-slate-400"
|
|
270
|
-
:title="'
|
|
290
|
+
:title="t('testing.fixerAttempts')"
|
|
271
291
|
>
|
|
272
|
-
{{
|
|
273
|
-
|
|
292
|
+
{{
|
|
293
|
+
t('testing.fixCount', { attempts: testState.attempts, max: testState.maxAttempts })
|
|
294
|
+
}}
|
|
295
|
+
<template v-if="testState.phase === 'fixing'">
|
|
296
|
+
{{ t('testing.fixingSuffix') }}</template
|
|
297
|
+
>
|
|
274
298
|
</span>
|
|
275
299
|
<StepRestartControl
|
|
276
300
|
:instance-id="instanceId"
|
|
@@ -293,10 +317,9 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
293
317
|
class="flex h-full flex-col items-center justify-center gap-2 text-center text-slate-400"
|
|
294
318
|
>
|
|
295
319
|
<UIcon name="i-lucide-flask-conical" class="h-8 w-8 opacity-40" />
|
|
296
|
-
<p class="text-sm">
|
|
320
|
+
<p class="text-sm">{{ t('testing.empty.title') }}</p>
|
|
297
321
|
<p class="max-w-sm text-[11px] text-slate-500">
|
|
298
|
-
|
|
299
|
-
live progress on the board.
|
|
322
|
+
{{ t('testing.empty.hint') }}
|
|
300
323
|
</p>
|
|
301
324
|
</div>
|
|
302
325
|
|
|
@@ -307,7 +330,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
307
330
|
</p>
|
|
308
331
|
|
|
309
332
|
<h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
310
|
-
|
|
333
|
+
{{ t('testing.scenariosOutcomes') }}
|
|
311
334
|
</h3>
|
|
312
335
|
<ul class="space-y-2">
|
|
313
336
|
<li
|
|
@@ -340,12 +363,21 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
340
363
|
v-if="g.screenshots.length"
|
|
341
364
|
name="i-lucide-camera"
|
|
342
365
|
class="h-3.5 w-3.5 shrink-0 text-slate-500"
|
|
343
|
-
:title="
|
|
366
|
+
:title="
|
|
367
|
+
t(
|
|
368
|
+
'testing.screenshotCount',
|
|
369
|
+
{ count: g.screenshots.length },
|
|
370
|
+
g.screenshots.length,
|
|
371
|
+
)
|
|
372
|
+
"
|
|
344
373
|
/>
|
|
345
374
|
<span class="shrink-0 text-[11px] text-slate-500">
|
|
346
|
-
{{ g.outcomes.length }
|
|
375
|
+
{{ t('testing.checkCount', { count: g.outcomes.length }, g.outcomes.length) }}
|
|
347
376
|
<template v-if="g.concerns.length">
|
|
348
|
-
·
|
|
377
|
+
·
|
|
378
|
+
{{
|
|
379
|
+
t('testing.concernCount', { count: g.concerns.length }, g.concerns.length)
|
|
380
|
+
}}
|
|
349
381
|
</template>
|
|
350
382
|
</span>
|
|
351
383
|
</button>
|
|
@@ -370,7 +402,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
370
402
|
</div>
|
|
371
403
|
</div>
|
|
372
404
|
<p v-if="!g.outcomes.length" class="py-0.5 text-[12px] italic text-slate-500">
|
|
373
|
-
|
|
405
|
+
{{ t('testing.noDiscreteCheck') }}
|
|
374
406
|
</p>
|
|
375
407
|
|
|
376
408
|
<!-- Concerns linked to this scenario -->
|
|
@@ -391,7 +423,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
391
423
|
class="rounded px-1 text-[10px] uppercase"
|
|
392
424
|
:class="SEVERITY_META[c.severity].chip"
|
|
393
425
|
>
|
|
394
|
-
{{ c.severity }}
|
|
426
|
+
{{ SEVERITY_LABELS[c.severity] }}
|
|
395
427
|
</span>
|
|
396
428
|
</div>
|
|
397
429
|
<p v-if="c.detail" class="text-[12px] leading-snug text-slate-400">
|
|
@@ -412,14 +444,18 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
412
444
|
<img
|
|
413
445
|
v-if="blobs.urlFor(s.artifactId)"
|
|
414
446
|
:src="blobs.urlFor(s.artifactId)"
|
|
415
|
-
:alt="
|
|
447
|
+
:alt="t('testing.screenshotAlt', { view: s.view })"
|
|
416
448
|
class="h-full w-full object-cover object-top"
|
|
417
449
|
/>
|
|
418
450
|
<span
|
|
419
451
|
v-else
|
|
420
452
|
class="flex h-full w-full items-center justify-center text-[10px] text-slate-600"
|
|
421
453
|
>
|
|
422
|
-
{{
|
|
454
|
+
{{
|
|
455
|
+
blobs.statusFor(s.artifactId) === 'error'
|
|
456
|
+
? t('testing.shot.failed')
|
|
457
|
+
: t('testing.shot.loading')
|
|
458
|
+
}}
|
|
423
459
|
</span>
|
|
424
460
|
<span
|
|
425
461
|
class="absolute inset-x-0 bottom-0 truncate bg-slate-950/80 px-1 py-0.5 text-[9px] text-slate-300"
|
|
@@ -434,7 +470,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
434
470
|
<!-- Standalone gallery: any captures not mapped to a scenario above -->
|
|
435
471
|
<section v-if="ungroupedScreenshots.length" class="mt-5">
|
|
436
472
|
<h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
437
|
-
|
|
473
|
+
{{ t('testing.screenshots') }}
|
|
438
474
|
</h3>
|
|
439
475
|
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3">
|
|
440
476
|
<button
|
|
@@ -447,7 +483,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
447
483
|
<img
|
|
448
484
|
v-if="blobs.urlFor(s.artifactId)"
|
|
449
485
|
:src="blobs.urlFor(s.artifactId)"
|
|
450
|
-
:alt="
|
|
486
|
+
:alt="t('testing.screenshotAlt', { view: s.view })"
|
|
451
487
|
class="h-full w-full object-cover object-top"
|
|
452
488
|
/>
|
|
453
489
|
<span
|
|
@@ -455,7 +491,9 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
455
491
|
class="flex h-full w-full items-center justify-center text-[11px] text-slate-600"
|
|
456
492
|
>
|
|
457
493
|
{{
|
|
458
|
-
blobs.statusFor(s.artifactId) === 'error'
|
|
494
|
+
blobs.statusFor(s.artifactId) === 'error'
|
|
495
|
+
? t('testing.shot.failedToLoad')
|
|
496
|
+
: t('testing.shot.loading')
|
|
459
497
|
}}
|
|
460
498
|
</span>
|
|
461
499
|
<span
|
|
@@ -474,7 +512,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
474
512
|
>
|
|
475
513
|
<div v-if="report">
|
|
476
514
|
<h4 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
477
|
-
|
|
515
|
+
{{ t('testing.verdict.heading') }}
|
|
478
516
|
</h4>
|
|
479
517
|
<div class="flex items-center gap-2 text-[13px]">
|
|
480
518
|
<UIcon
|
|
@@ -483,33 +521,39 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
483
521
|
:class="report.greenlight ? 'text-emerald-400' : 'text-rose-400'"
|
|
484
522
|
/>
|
|
485
523
|
<span :class="report.greenlight ? 'text-emerald-300' : 'text-rose-300'">
|
|
486
|
-
{{
|
|
524
|
+
{{
|
|
525
|
+
report.greenlight ? t('testing.verdict.safe') : t('testing.verdict.withheld')
|
|
526
|
+
}}
|
|
487
527
|
</span>
|
|
488
528
|
</div>
|
|
489
529
|
</div>
|
|
490
530
|
|
|
491
531
|
<div v-if="report">
|
|
492
532
|
<h4 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
493
|
-
|
|
533
|
+
{{ t('testing.outcomes.heading') }}
|
|
494
534
|
</h4>
|
|
495
535
|
<dl class="space-y-1 text-[12px]">
|
|
496
536
|
<div class="flex items-center justify-between">
|
|
497
|
-
<dt class="text-slate-400">
|
|
537
|
+
<dt class="text-slate-400">{{ t('testing.outcomes.passed') }}</dt>
|
|
498
538
|
<dd class="text-emerald-300">{{ counts.passed }}</dd>
|
|
499
539
|
</div>
|
|
500
540
|
<div class="flex items-center justify-between">
|
|
501
|
-
<dt class="text-slate-400">
|
|
541
|
+
<dt class="text-slate-400">{{ t('testing.outcomes.failed') }}</dt>
|
|
502
542
|
<dd class="text-rose-300">{{ counts.failed }}</dd>
|
|
503
543
|
</div>
|
|
504
544
|
<div class="flex items-center justify-between">
|
|
505
|
-
<dt class="text-slate-400">
|
|
545
|
+
<dt class="text-slate-400">{{ t('testing.outcomes.skipped') }}</dt>
|
|
506
546
|
<dd class="text-slate-300">{{ counts.skipped }}</dd>
|
|
507
547
|
</div>
|
|
508
548
|
<div class="flex items-center justify-between border-t border-slate-800 pt-1">
|
|
509
|
-
<dt class="text-slate-400">
|
|
549
|
+
<dt class="text-slate-400">{{ t('testing.outcomes.concerns') }}</dt>
|
|
510
550
|
<dd class="text-amber-300">
|
|
511
551
|
{{ counts.concerns
|
|
512
|
-
}}<template v-if="counts.blocking">
|
|
552
|
+
}}<template v-if="counts.blocking">
|
|
553
|
+
{{
|
|
554
|
+
t('testing.outcomes.blocking', { count: counts.blocking }, counts.blocking)
|
|
555
|
+
}}</template
|
|
556
|
+
>
|
|
513
557
|
</dd>
|
|
514
558
|
</div>
|
|
515
559
|
</dl>
|
|
@@ -517,7 +561,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
517
561
|
|
|
518
562
|
<div v-if="report?.environment">
|
|
519
563
|
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
520
|
-
|
|
564
|
+
{{ t('testing.environment') }}
|
|
521
565
|
</h4>
|
|
522
566
|
<p class="text-[12px] capitalize text-slate-300">{{ report.environment }}</p>
|
|
523
567
|
</div>
|
|
@@ -535,8 +579,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
535
579
|
/>
|
|
536
580
|
|
|
537
581
|
<p class="mt-auto text-[10px] leading-relaxed text-slate-600">
|
|
538
|
-
|
|
539
|
-
Outcomes and concerns are grouped under them by name.
|
|
582
|
+
{{ t('testing.footer') }}
|
|
540
583
|
</p>
|
|
541
584
|
</aside>
|
|
542
585
|
</div>
|