@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.
@@ -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 { HumanTestEnvironmentStatus, HumanTestStepState } from '~/types/execution'
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
- const ENV_STATUS_META: Record<HumanTestEnvironmentStatus, { label: string; color: string }> = {
42
- provisioning: { label: 'Provisioning…', color: 'text-amber-300' },
43
- ready: { label: 'Ready', color: 'text-emerald-300' },
44
- failed: { label: 'Failed', color: 'text-rose-300' },
45
- expired: { label: 'Expired', color: 'text-slate-400' },
46
- tearing_down: { label: 'Tearing down…', color: 'text-slate-400' },
47
- torn_down: { label: 'Destroyed', color: 'text-slate-400' },
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: 'Provisioning environment…',
52
- awaiting_human: 'Awaiting your validation',
53
- fixing: 'Fixer is addressing your findings…',
54
- resolving_conflicts: 'Resolving conflicts with main…',
55
- passed: '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
- Human testing{{ block ? ` — ${block.title}` : '' }}
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] : 'Validate the change in a live environment' }}
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">This step hasn't started yet.</p>
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
- Ephemeral environment
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="ENV_STATUS_META[env.status].color"
177
+ :class="ENV_STATUS_COLOR[env.status]"
153
178
  />
154
- <span :class="ENV_STATUS_META[env.status].color">{{
155
- ENV_STATUS_META[env.status].label
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">No URL yet.</p>
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
- Expires {{ new Date(env.expiresAt).toLocaleString() }}
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 ?? 'No live environment.' }}
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
- Recreate
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
- Destroy
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
- Pull main + redeploy
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
- Found a problem?
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 ? 'Cancel' : 'Request a fix' }}
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="Describe what went wrong — the Fixer agent gets this as context, then the environment is rebuilt for re-testing."
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
- Send to Fixer
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
- History ({{ ht.attempts }} round{{ ht.attempts === 1 ? '' : 's' }})
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' ? 'Fix requested' : 'Pulled main'
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
- {{ r.outcome ?? 'in progress' }}
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
- Looks good — continue
356
+ {{ t('humanTest.confirm') }}
322
357
  </UButton>
323
358
  </footer>
324
359
  </div>