@cat-factory/app 0.47.9 → 0.47.11

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.
@@ -18,6 +18,7 @@ import StepRunMeta from '~/components/panels/StepRunMeta.vue'
18
18
  const board = useBoardStore()
19
19
  const execution = useExecutionStore()
20
20
  const visualConfirm = useVisualConfirmStore()
21
+ const { t } = useI18n()
21
22
 
22
23
  // Per-window blob cache; release the cached screenshot/reference object URLs when the window
23
24
  // goes away, so the (potentially large) blob bytes don't linger for the rest of the session.
@@ -41,11 +42,18 @@ const busy = computed(() => (blockId.value ? visualConfirm.isBusy(blockId.value)
41
42
  const awaitingHuman = computed(() => phase.value === 'awaiting_human')
42
43
  const working = computed(() => phase.value === 'fixing')
43
44
 
44
- const PHASE_LABEL: Record<NonNullable<VisualConfirmStepState['phase']>, string> = {
45
- awaiting_human: 'Awaiting your review',
46
- fixing: 'Fixer is addressing your findings…',
47
- approved: 'Approved',
48
- }
45
+ const PHASE_LABEL = computed<Record<NonNullable<VisualConfirmStepState['phase']>, string>>(() => ({
46
+ awaiting_human: t('visualConfirm.phase.awaiting_human'),
47
+ fixing: t('visualConfirm.phase.fixing'),
48
+ approved: t('visualConfirm.phase.approved'),
49
+ }))
50
+
51
+ // Exhaustive map of a round's outcome enum → label (literal keys keep the typed-key drift
52
+ // guard live, vs a runtime-built `visualConfirm.outcome.${outcome}`).
53
+ const OUTCOME_LABELS = computed<Record<'completed' | 'failed', string>>(() => ({
54
+ completed: t('visualConfirm.outcome.completed'),
55
+ failed: t('visualConfirm.outcome.failed'),
56
+ }))
49
57
 
50
58
  // Resolve every pair's artifacts (the gallery + the lightbox share this one cache).
51
59
  watch(
@@ -66,14 +74,14 @@ const lightboxItems = computed(() => {
66
74
  if (p.actualArtifactId)
67
75
  items.push({
68
76
  artifactId: p.actualArtifactId,
69
- label: `${p.view} — actual`,
70
- alt: `${p.view} (actual)`,
77
+ label: t('visualConfirm.lightbox.actualLabel', { view: p.view }),
78
+ alt: t('visualConfirm.lightbox.actualAlt', { view: p.view }),
71
79
  })
72
80
  if (p.referenceArtifactId)
73
81
  items.push({
74
82
  artifactId: p.referenceArtifactId,
75
- label: `${p.view} — reference`,
76
- alt: `${p.view} (reference)`,
83
+ label: t('visualConfirm.lightbox.referenceLabel', { view: p.view }),
84
+ alt: t('visualConfirm.lightbox.referenceAlt', { view: p.view }),
77
85
  })
78
86
  }
79
87
  return items
@@ -189,7 +197,7 @@ const canApprove = computed(
189
197
  tabindex="-1"
190
198
  role="dialog"
191
199
  aria-modal="true"
192
- aria-label="Visual confirmation"
200
+ :aria-label="t('visualConfirm.ariaLabel')"
193
201
  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"
194
202
  >
195
203
  <header class="flex items-center gap-3 border-b border-slate-800 px-5 py-3">
@@ -200,10 +208,14 @@ const canApprove = computed(
200
208
  </span>
201
209
  <div class="min-w-0 flex-1">
202
210
  <h2 class="truncate text-sm font-semibold text-slate-100">
203
- Visual confirmation{{ block ? ` — ${block.title}` : '' }}
211
+ {{
212
+ block
213
+ ? t('visualConfirm.titleWithBlock', { title: block.title })
214
+ : t('visualConfirm.title')
215
+ }}
204
216
  </h2>
205
217
  <p class="truncate text-[11px] text-slate-400">
206
- {{ phase ? PHASE_LABEL[phase] : 'Review the UI against the reference designs' }}
218
+ {{ phase ? PHASE_LABEL[phase] : t('visualConfirm.subtitle') }}
207
219
  </p>
208
220
  </div>
209
221
  <button
@@ -220,7 +232,7 @@ const canApprove = computed(
220
232
  class="flex flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
221
233
  >
222
234
  <UIcon name="i-lucide-image-play" class="h-8 w-8 opacity-40" />
223
- <p class="text-sm">This step hasn't started yet.</p>
235
+ <p class="text-sm">{{ t('visualConfirm.notStarted') }}</p>
224
236
  </div>
225
237
 
226
238
  <template v-else>
@@ -262,38 +274,37 @@ const canApprove = computed(
262
274
  :name="noteOpen[p.view] ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
263
275
  class="h-3 w-3"
264
276
  />
265
- Note an issue with {{ p.view }}
277
+ {{ t('visualConfirm.noteIssue', { view: p.view }) }}
266
278
  <span
267
279
  v-if="perViewNotes[p.view]?.trim()"
268
280
  class="rounded-full bg-amber-500/15 px-1.5 text-[9px] text-amber-300"
269
- >noted</span
281
+ >{{ t('visualConfirm.noted') }}</span
270
282
  >
271
283
  </button>
272
284
  <textarea
273
285
  v-if="noteOpen[p.view]"
274
286
  v-model="perViewNotes[p.view]"
275
287
  rows="2"
276
- :placeholder="`What looks wrong on ${p.view}?`"
288
+ :placeholder="t('visualConfirm.notePlaceholder', { view: p.view })"
277
289
  class="mt-1 w-full rounded-md border border-slate-700 bg-slate-950 px-2 py-1.5 text-[12px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none"
278
290
  />
279
291
  </div>
280
292
  </div>
281
293
  </section>
282
294
  <p v-else class="text-[12px] italic text-slate-500">
283
- No screenshots were captured — review the change manually, or upload a reference
284
- below.
295
+ {{ t('visualConfirm.noScreenshots') }}
285
296
  </p>
286
297
 
287
298
  <!-- Upload a reference for any view -->
288
299
  <section class="rounded-lg border border-slate-800 bg-slate-900/60 p-3">
289
300
  <h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
290
- Upload a reference design
301
+ {{ t('visualConfirm.upload.heading') }}
291
302
  </h3>
292
303
  <div class="flex flex-wrap items-center gap-2">
293
304
  <input
294
305
  v-model="uploadView"
295
306
  list="vc-views"
296
- placeholder="View name (e.g. login)"
307
+ :placeholder="t('visualConfirm.upload.viewPlaceholder')"
297
308
  class="rounded-md border border-slate-700 bg-slate-950 px-2 py-1 text-[12px] text-slate-200 placeholder:text-slate-600"
298
309
  />
299
310
  <datalist id="vc-views">
@@ -311,8 +322,8 @@ const canApprove = computed(
311
322
  <p class="mt-1.5 text-[10px] text-slate-600">
312
323
  {{
313
324
  uploadView.trim()
314
- ? 'Tip: drop an image straight onto a pair above to set its reference.'
315
- : 'Enter a view name first, then choose a file. Or drop an image straight onto a pair above.'
325
+ ? t('visualConfirm.upload.tipReady')
326
+ : t('visualConfirm.upload.tipNeedView')
316
327
  }}
317
328
  </p>
318
329
  </section>
@@ -323,17 +334,17 @@ const canApprove = computed(
323
334
  class="rounded-lg border border-slate-800 bg-slate-900/60 p-3"
324
335
  >
325
336
  <h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
326
- Request a fix
337
+ {{ t('visualConfirm.requestFix.heading') }}
327
338
  </h3>
328
339
  <textarea
329
340
  v-model="globalFindings"
330
341
  rows="3"
331
- placeholder="Anything else the Fixer should know (in addition to any per-view notes above)."
342
+ :placeholder="t('visualConfirm.requestFix.placeholder')"
332
343
  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"
333
344
  />
334
345
  <div class="mt-2 flex items-center justify-between">
335
346
  <span class="text-[11px] text-slate-500">
336
- Per-view notes are folded in automatically.
347
+ {{ t('visualConfirm.requestFix.foldedHint') }}
337
348
  </span>
338
349
  <UButton
339
350
  size="sm"
@@ -343,7 +354,7 @@ const canApprove = computed(
343
354
  :disabled="busy || !hasFindings"
344
355
  @click="submitFix"
345
356
  >
346
- Send to Fixer
357
+ {{ t('visualConfirm.requestFix.send') }}
347
358
  </UButton>
348
359
  </div>
349
360
  </section>
@@ -354,7 +365,7 @@ const canApprove = computed(
354
365
  class="rounded-lg border border-slate-800 bg-slate-900/60 p-3"
355
366
  >
356
367
  <h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
357
- History ({{ vc.attempts }} round{{ vc.attempts === 1 ? '' : 's' }})
368
+ {{ t('visualConfirm.history.heading', { count: vc.attempts }, vc.attempts) }}
358
369
  </h3>
359
370
  <ol class="space-y-2">
360
371
  <li v-for="(r, i) in vc.rounds" :key="i" class="flex items-start gap-2 text-[12px]">
@@ -363,7 +374,9 @@ const canApprove = computed(
363
374
  class="mt-0.5 h-3.5 w-3.5 shrink-0 text-slate-400"
364
375
  />
365
376
  <div class="min-w-0 flex-1">
366
- <span class="text-slate-200">Fix requested</span>
377
+ <span class="text-slate-200">{{
378
+ t('visualConfirm.history.fixRequested')
379
+ }}</span>
367
380
  <span
368
381
  class="ml-1.5 rounded px-1 text-[10px] uppercase"
369
382
  :class="
@@ -374,7 +387,11 @@ const canApprove = computed(
374
387
  : 'bg-slate-500/15 text-slate-300'
375
388
  "
376
389
  >
377
- {{ r.outcome ?? 'in progress' }}
390
+ {{
391
+ r.outcome
392
+ ? OUTCOME_LABELS[r.outcome]
393
+ : t('visualConfirm.outcome.inProgress')
394
+ }}
378
395
  </span>
379
396
  <p v-if="r.findings" class="whitespace-pre-wrap leading-snug text-slate-400">
380
397
  {{ r.findings }}
@@ -405,7 +422,7 @@ const canApprove = computed(
405
422
  class="flex items-center gap-1.5 text-[11px] text-amber-300/90"
406
423
  >
407
424
  <input v-model="ackDegraded" type="checkbox" class="accent-amber-500" />
408
- I've reviewed this manually
425
+ {{ t('visualConfirm.reviewedManually') }}
409
426
  </label>
410
427
  <UButton
411
428
  size="sm"
@@ -416,7 +433,7 @@ const canApprove = computed(
416
433
  :disabled="busy || !awaitingHuman"
417
434
  @click="recapture"
418
435
  >
419
- Recapture
436
+ {{ t('visualConfirm.recapture') }}
420
437
  </UButton>
421
438
  <UButton
422
439
  color="primary"
@@ -425,7 +442,7 @@ const canApprove = computed(
425
442
  :disabled="!canApprove"
426
443
  @click="approve"
427
444
  >
428
- Approve continue
445
+ {{ t('visualConfirm.approve') }}
429
446
  </UButton>
430
447
  </div>
431
448
  </footer>