@cat-factory/app 0.46.9 → 0.46.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.
Files changed (42) hide show
  1. package/app/components/board/AddTaskModal.vue +106 -88
  2. package/app/components/board/AgentFailureCard.vue +8 -5
  3. package/app/components/board/AgentStopButton.vue +8 -5
  4. package/app/components/board/BoardCanvas.vue +13 -9
  5. package/app/components/board/RecurringPipelineModal.vue +33 -23
  6. package/app/components/board/nodes/BlockNode.vue +50 -34
  7. package/app/components/board/nodes/DecisionBadge.vue +6 -3
  8. package/app/components/board/nodes/DraggableTask.vue +2 -1
  9. package/app/components/board/nodes/EpicNode.vue +10 -3
  10. package/app/components/board/nodes/ModuleFrame.vue +6 -5
  11. package/app/components/board/nodes/TaskCard.vue +40 -22
  12. package/app/components/board/nodes/TaskPipelineMini.vue +4 -3
  13. package/app/components/layout/BoardToolbar.vue +26 -19
  14. package/app/components/observability/StepMetricsBar.vue +31 -11
  15. package/app/components/observability/StepModelActivity.vue +3 -2
  16. package/app/components/panels/AgentStepDetail.vue +52 -39
  17. package/app/components/panels/DecisionModal.vue +12 -5
  18. package/app/components/panels/GenericStructuredResultView.vue +16 -6
  19. package/app/components/panels/InspectorPanel.vue +35 -28
  20. package/app/components/panels/ObservabilityPanel.vue +92 -49
  21. package/app/components/panels/StepMetadataCard.vue +87 -30
  22. package/app/components/panels/StepRestartControl.vue +4 -3
  23. package/app/components/panels/StepRunMeta.vue +23 -10
  24. package/app/components/panels/StepTestReport.vue +14 -11
  25. package/app/components/panels/inspector/ContainerSummary.vue +25 -14
  26. package/app/components/panels/inspector/EpicChildren.vue +7 -4
  27. package/app/components/panels/inspector/RecurringScheduleSettings.vue +60 -19
  28. package/app/components/panels/inspector/ServiceFragments.vue +3 -2
  29. package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +18 -13
  30. package/app/components/panels/inspector/ServiceTestConfig.vue +50 -39
  31. package/app/components/panels/inspector/TaskAgentConfig.vue +6 -5
  32. package/app/components/panels/inspector/TaskDependencies.vue +9 -4
  33. package/app/components/panels/inspector/TaskEstimateBadge.vue +14 -13
  34. package/app/components/panels/inspector/TaskExecution.vue +65 -31
  35. package/app/components/panels/inspector/TaskRunSettings.vue +93 -56
  36. package/app/components/panels/inspector/TaskStructure.vue +5 -4
  37. package/i18n/locales/en.json +606 -0
  38. package/i18n/locales/es.json +596 -2
  39. package/i18n/locales/fr.json +596 -2
  40. package/i18n/locales/pl.json +596 -2
  41. package/i18n/locales/uk.json +596 -2
  42. package/package.json +1 -1
@@ -15,6 +15,7 @@ const accounts = useAccountsStore()
15
15
  const tracker = useTrackerStore()
16
16
  const ui = useUiStore()
17
17
  const { ready, unavailableInPreset } = useAiReadiness()
18
+ const { t, n } = useI18n()
18
19
 
19
20
  // ---- responsible product person --------------------------------------------
20
21
  // The account member (a `product` role-holder) accountable for this task; they are
@@ -34,7 +35,11 @@ const responsibleLabel = computed(() => {
34
35
  })
35
36
  const responsibleMenu = computed(() => [
36
37
  [
37
- { label: 'Unassigned', icon: 'i-lucide-user-x', onSelect: () => setResponsible('') },
38
+ {
39
+ label: t('inspector.runSettings.unassigned'),
40
+ icon: 'i-lucide-user-x',
41
+ onSelect: () => setResponsible(''),
42
+ },
38
43
  ...productMembers.value.map((m) => ({
39
44
  label: m.name || m.email || m.userId,
40
45
  icon: 'i-lucide-user',
@@ -62,8 +67,11 @@ const presetMenu = computed(() => [
62
67
  [
63
68
  {
64
69
  label: mergePresets.defaultPreset
65
- ? `Default (${mergePresets.defaultPreset.name}) — ${mergePresetThresholds(mergePresets.defaultPreset)}`
66
- : 'Workspace default',
70
+ ? t('inspector.runSettings.defaultPresetThresholds', {
71
+ name: mergePresets.defaultPreset.name,
72
+ thresholds: mergePresetThresholds(mergePresets.defaultPreset),
73
+ })
74
+ : t('inspector.runSettings.workspaceDefault'),
67
75
  icon: 'i-lucide-rotate-ccw',
68
76
  onSelect: () => setPreset(''),
69
77
  },
@@ -99,8 +107,8 @@ const modelPresetMenu = computed(() => [
99
107
  [
100
108
  {
101
109
  label: modelPresets.defaultPreset
102
- ? `Default (${modelPresets.defaultPreset.name})`
103
- : 'Workspace default',
110
+ ? t('inspector.runSettings.defaultPreset', { name: modelPresets.defaultPreset.name })
111
+ : t('inspector.runSettings.workspaceDefault'),
104
112
  icon: 'i-lucide-rotate-ccw',
105
113
  onSelect: () => setModelPreset(''),
106
114
  },
@@ -124,7 +132,7 @@ const selectedPipeline = computed(() =>
124
132
  const pipelineMenu = computed(() => [
125
133
  [
126
134
  {
127
- label: 'No default',
135
+ label: t('inspector.runSettings.noDefault'),
128
136
  icon: 'i-lucide-rotate-ccw',
129
137
  onSelect: () => setPipeline(''),
130
138
  },
@@ -151,9 +159,13 @@ function setResolveOnMerge(value: WritebackOverride | null) {
151
159
  function writebackMenu(set: (value: WritebackOverride | null) => void) {
152
160
  return [
153
161
  [
154
- { label: 'Inherit workspace', icon: 'i-lucide-rotate-ccw', onSelect: () => set(null) },
155
- { label: 'On', icon: 'i-lucide-check', onSelect: () => set('on') },
156
- { label: 'Off', icon: 'i-lucide-x', onSelect: () => set('off') },
162
+ {
163
+ label: t('inspector.runSettings.inheritWorkspace'),
164
+ icon: 'i-lucide-rotate-ccw',
165
+ onSelect: () => set(null),
166
+ },
167
+ { label: t('inspector.runSettings.on'), icon: 'i-lucide-check', onSelect: () => set('on') },
168
+ { label: t('inspector.runSettings.off'), icon: 'i-lucide-x', onSelect: () => set('off') },
157
169
  ],
158
170
  ]
159
171
  }
@@ -162,9 +174,9 @@ function writebackLabel(
162
174
  override: WritebackOverride | null | undefined,
163
175
  wsDefault: boolean,
164
176
  ): string {
165
- if (override === 'on') return 'On'
166
- if (override === 'off') return 'Off'
167
- return `Inherit (${wsDefault ? 'on' : 'off'})`
177
+ if (override === 'on') return t('inspector.runSettings.on')
178
+ if (override === 'off') return t('inspector.runSettings.off')
179
+ return wsDefault ? t('inspector.runSettings.inheritOn') : t('inspector.runSettings.inheritOff')
168
180
  }
169
181
 
170
182
  const commentOnPrOpenLabel = computed(() =>
@@ -182,21 +194,29 @@ const resolveOnMergeLabel = computed(() =>
182
194
  function setTechnical(value: boolean | null) {
183
195
  board.updateBlock(props.block.id, { technical: value })
184
196
  }
185
- const technicalMenu = [
197
+ const technicalMenu = computed(() => [
186
198
  [
187
199
  {
188
- label: 'Unset (auto-detect)',
200
+ label: t('inspector.runSettings.technical.unset'),
189
201
  icon: 'i-lucide-rotate-ccw',
190
202
  onSelect: () => setTechnical(null),
191
203
  },
192
- { label: 'Technical', icon: 'i-lucide-wrench', onSelect: () => setTechnical(true) },
193
- { label: 'Business', icon: 'i-lucide-briefcase', onSelect: () => setTechnical(false) },
204
+ {
205
+ label: t('inspector.runSettings.technical.technical'),
206
+ icon: 'i-lucide-wrench',
207
+ onSelect: () => setTechnical(true),
208
+ },
209
+ {
210
+ label: t('inspector.runSettings.technical.business'),
211
+ icon: 'i-lucide-briefcase',
212
+ onSelect: () => setTechnical(false),
213
+ },
194
214
  ],
195
- ]
215
+ ])
196
216
  const technicalLabel = computed(() => {
197
- if (props.block.technical === true) return 'Technical'
198
- if (props.block.technical === false) return 'Business'
199
- return 'Unset (auto-detect)'
217
+ if (props.block.technical === true) return t('inspector.runSettings.technical.technical')
218
+ if (props.block.technical === false) return t('inspector.runSettings.technical.business')
219
+ return t('inspector.runSettings.technical.unset')
200
220
  })
201
221
  </script>
202
222
 
@@ -206,7 +226,7 @@ const technicalLabel = computed(() => {
206
226
  <div>
207
227
  <div class="mb-1 flex items-center justify-between">
208
228
  <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
209
- Pipeline
229
+ {{ t('inspector.runSettings.pipeline') }}
210
230
  </span>
211
231
  <UDropdownMenu :items="pipelineMenu">
212
232
  <UButton
@@ -230,7 +250,7 @@ const technicalLabel = computed(() => {
230
250
  </UBadge>
231
251
  </div>
232
252
  <div v-else class="text-[11px] text-slate-500">
233
- No default — pick a pipeline when you run this task.
253
+ {{ t('inspector.runSettings.pipelineEmpty') }}
234
254
  </div>
235
255
  </div>
236
256
 
@@ -238,7 +258,7 @@ const technicalLabel = computed(() => {
238
258
  <div>
239
259
  <div class="mb-1 flex items-center justify-between">
240
260
  <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
241
- Merge policy
261
+ {{ t('inspector.runSettings.mergePolicy') }}
242
262
  </span>
243
263
  <UDropdownMenu :items="presetMenu">
244
264
  <UButton
@@ -251,15 +271,21 @@ const technicalLabel = computed(() => {
251
271
  </UDropdownMenu>
252
272
  </div>
253
273
  <div v-if="selectedPreset" class="text-[11px] text-slate-400">
254
- <span class="text-slate-300">{{ selectedPreset.name }}</span>
255
- auto-merge when complexity ≤ {{ Math.round(selectedPreset.maxComplexity * 100) }}%, risk ≤
256
- {{ Math.round(selectedPreset.maxRisk * 100) }}%, impact ≤
257
- {{ Math.round(selectedPreset.maxImpact * 100) }}%; up to
258
- {{ selectedPreset.ciMaxAttempts }} CI-fix attempts.
259
- <span v-if="!block.mergePresetId" class="text-slate-500">(workspace default)</span>
274
+ <i18n-t keypath="inspector.runSettings.mergePresetDetail" tag="span" scope="global">
275
+ <template #name>
276
+ <span class="text-slate-300">{{ selectedPreset.name }}</span>
277
+ </template>
278
+ <template #complexity>{{ n(selectedPreset.maxComplexity, { key: 'percent' }) }}</template>
279
+ <template #risk>{{ n(selectedPreset.maxRisk, { key: 'percent' }) }}</template>
280
+ <template #impact>{{ n(selectedPreset.maxImpact, { key: 'percent' }) }}</template>
281
+ <template #attempts>{{ selectedPreset.ciMaxAttempts }}</template>
282
+ </i18n-t>
283
+ <span v-if="!block.mergePresetId" class="text-slate-500">{{
284
+ t('inspector.runSettings.workspaceDefaultParen')
285
+ }}</span>
260
286
  </div>
261
287
  <div v-else class="text-[11px] text-slate-500">
262
- No preset configured — the merger raises a review notification for every PR.
288
+ {{ t('inspector.runSettings.mergePresetEmpty') }}
263
289
  </div>
264
290
  </div>
265
291
 
@@ -267,7 +293,7 @@ const technicalLabel = computed(() => {
267
293
  <div>
268
294
  <div class="mb-1 flex items-center justify-between">
269
295
  <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
270
- Model preset
296
+ {{ t('inspector.runSettings.modelPreset') }}
271
297
  </span>
272
298
  <UDropdownMenu :items="modelPresetMenu">
273
299
  <UButton
@@ -281,14 +307,21 @@ const technicalLabel = computed(() => {
281
307
  </div>
282
308
  <div v-if="selectedModelPreset" class="text-[11px] text-slate-400">
283
309
  <span class="text-slate-300">{{ selectedModelPreset.name }}</span>
284
- base {{ selectedModelPreset.baseModelId
285
- }}<span v-if="Object.keys(selectedModelPreset.overrides).length">
286
- , {{ Object.keys(selectedModelPreset.overrides).length }} override(s)</span
310
+ {{ t('inspector.runSettings.modelPresetBase', { model: selectedModelPreset.baseModelId })
311
+ }}<span v-if="Object.keys(selectedModelPreset.overrides).length">{{
312
+ t(
313
+ 'inspector.runSettings.modelPresetOverrides',
314
+ { count: Object.keys(selectedModelPreset.overrides).length },
315
+ Object.keys(selectedModelPreset.overrides).length,
316
+ )
317
+ }}</span
287
318
  >.
288
- <span v-if="!block.modelPresetId" class="text-slate-500">(workspace default)</span>
319
+ <span v-if="!block.modelPresetId" class="text-slate-500">{{
320
+ t('inspector.runSettings.workspaceDefaultParen')
321
+ }}</span>
289
322
  </div>
290
323
  <div v-else class="text-[11px] text-slate-500">
291
- No preset configured — agents run on the deployment's default routing.
324
+ {{ t('inspector.runSettings.modelPresetEmpty') }}
292
325
  </div>
293
326
  <div
294
327
  v-if="unavailablePresetModels.length"
@@ -301,29 +334,31 @@ const technicalLabel = computed(() => {
301
334
  />
302
335
  <div class="min-w-0">
303
336
  <p>
304
- Not available under the current configuration:
305
- <span class="text-amber-100">{{ unavailablePresetModels.join(', ') }}</span
306
- >. This task would fail on those steps.
337
+ <i18n-t keypath="inspector.runSettings.unavailableModels" tag="span" scope="global">
338
+ <template #models>
339
+ <span class="text-amber-100">{{ unavailablePresetModels.join(', ') }}</span>
340
+ </template>
341
+ </i18n-t>
307
342
  </p>
308
343
  <div class="mt-1.5 flex flex-wrap gap-2">
309
344
  <button
310
345
  class="font-medium text-amber-100 underline-offset-2 hover:underline"
311
346
  @click="ui.openModelConfig()"
312
347
  >
313
- Edit presets
348
+ {{ t('inspector.runSettings.editPresets') }}
314
349
  </button>
315
350
  <button
316
351
  class="font-medium text-amber-100 underline-offset-2 hover:underline"
317
352
  @click="ui.openVendorCredentials()"
318
353
  >
319
- Configure vendors
354
+ {{ t('inspector.runSettings.configureVendors') }}
320
355
  </button>
321
356
  </div>
322
357
  </div>
323
358
  </div>
324
359
  </div>
325
360
  <p class="mt-1 text-[11px] text-slate-500">
326
- Changing this affects only steps that haven't started yet.
361
+ {{ t('inspector.runSettings.modelPresetChangeHint') }}
327
362
  </p>
328
363
  </div>
329
364
 
@@ -331,7 +366,7 @@ const technicalLabel = computed(() => {
331
366
  <div>
332
367
  <div class="mb-1 flex items-center justify-between">
333
368
  <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
334
- Task kind
369
+ {{ t('inspector.runSettings.taskKind') }}
335
370
  </span>
336
371
  <UDropdownMenu :items="technicalMenu">
337
372
  <UButton
@@ -347,14 +382,13 @@ const technicalLabel = computed(() => {
347
382
  </div>
348
383
  <div class="text-[11px] text-slate-500">
349
384
  <template v-if="block.technical === true">
350
- Technical the implementer treats the task definition as primary and the spec as a
351
- regression reference; the spec-writer may produce no business specs.
385
+ {{ t('inspector.runSettings.technicalHint.technical') }}
352
386
  </template>
353
387
  <template v-else-if="block.technical === false">
354
- Business the specification leads, as usual.
388
+ {{ t('inspector.runSettings.technicalHint.business') }}
355
389
  </template>
356
390
  <template v-else>
357
- Auto-detect — inferred from the spec phase. Set it explicitly to override.
391
+ {{ t('inspector.runSettings.technicalHint.unset') }}
358
392
  </template>
359
393
  </div>
360
394
  </div>
@@ -363,12 +397,14 @@ const technicalLabel = computed(() => {
363
397
  <div>
364
398
  <div class="mb-1 flex items-center justify-between">
365
399
  <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
366
- Issue writeback
400
+ {{ t('inspector.runSettings.issueWriteback') }}
367
401
  </span>
368
402
  </div>
369
403
  <div class="space-y-1.5">
370
404
  <div class="flex items-center justify-between">
371
- <span class="text-[11px] text-slate-400">Comment on PR open</span>
405
+ <span class="text-[11px] text-slate-400">{{
406
+ t('inspector.runSettings.commentOnPrOpen')
407
+ }}</span>
372
408
  <UDropdownMenu :items="writebackMenu(setCommentOnPrOpen)">
373
409
  <UButton
374
410
  size="xs"
@@ -381,7 +417,9 @@ const technicalLabel = computed(() => {
381
417
  </UDropdownMenu>
382
418
  </div>
383
419
  <div class="flex items-center justify-between">
384
- <span class="text-[11px] text-slate-400">Close on merge</span>
420
+ <span class="text-[11px] text-slate-400">{{
421
+ t('inspector.runSettings.closeOnMerge')
422
+ }}</span>
385
423
  <UDropdownMenu :items="writebackMenu(setResolveOnMerge)">
386
424
  <UButton
387
425
  size="xs"
@@ -395,7 +433,7 @@ const technicalLabel = computed(() => {
395
433
  </div>
396
434
  </div>
397
435
  <div class="mt-1 text-[11px] text-slate-500">
398
- Writes back to this task's linked tracker issue. Overrides the workspace default.
436
+ {{ t('inspector.runSettings.writebackHint') }}
399
437
  </div>
400
438
  </div>
401
439
 
@@ -403,7 +441,7 @@ const technicalLabel = computed(() => {
403
441
  <div>
404
442
  <div class="mb-1 flex items-center justify-between">
405
443
  <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
406
- Responsible product
444
+ {{ t('inspector.runSettings.responsibleProduct') }}
407
445
  </span>
408
446
  <UDropdownMenu :items="responsibleMenu">
409
447
  <UButton
@@ -427,7 +465,7 @@ const technicalLabel = computed(() => {
427
465
  </UBadge>
428
466
  </div>
429
467
  <div v-else class="text-[11px] text-slate-500">
430
- Unassigned set a product owner to notify them when requirement review flags this task.
468
+ {{ t('inspector.runSettings.responsibleEmpty') }}
431
469
  </div>
432
470
  </div>
433
471
 
@@ -435,7 +473,7 @@ const technicalLabel = computed(() => {
435
473
  <div>
436
474
  <div class="flex items-center justify-between gap-2">
437
475
  <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
438
- Auto-start dependents
476
+ {{ t('inspector.runSettings.autoStartDependents') }}
439
477
  </span>
440
478
  <USwitch
441
479
  size="sm"
@@ -444,8 +482,7 @@ const technicalLabel = computed(() => {
444
482
  />
445
483
  </div>
446
484
  <div class="mt-1 text-[11px] text-slate-500">
447
- When this task merges, automatically start the tasks that depend on it (once their other
448
- dependencies are also done).
485
+ {{ t('inspector.runSettings.autoStartHint') }}
449
486
  </div>
450
487
  </div>
451
488
  </div>
@@ -5,6 +5,7 @@ const props = defineProps<{ block: Block }>()
5
5
 
6
6
  const board = useBoardStore()
7
7
  const fragments = useFragmentsStore()
8
+ const { t } = useI18n()
8
9
 
9
10
  // ---- best-practice prompt fragments ----------------------------------------
10
11
  // Selected fragments (resolved against the catalog; unknown ids are dropped).
@@ -47,13 +48,13 @@ function removeFragment(id: string) {
47
48
  <!-- module assignment -->
48
49
  <div>
49
50
  <div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
50
- Module
51
+ {{ t('inspector.structure.module') }}
51
52
  </div>
52
53
  <UInput
53
54
  v-model="block.moduleName"
54
55
  size="sm"
55
56
  class="w-full"
56
- placeholder="e.g. Sessions (created on implement if new)"
57
+ :placeholder="t('inspector.structure.modulePlaceholder')"
57
58
  icon="i-lucide-package"
58
59
  />
59
60
  </div>
@@ -62,7 +63,7 @@ function removeFragment(id: string) {
62
63
  <div>
63
64
  <div class="mb-1 flex items-center justify-between">
64
65
  <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
65
- Best practices
66
+ {{ t('inspector.structure.bestPractices') }}
66
67
  </span>
67
68
  <UDropdownMenu v-if="fragmentMenu.length" :items="fragmentMenu">
68
69
  <UButton
@@ -89,7 +90,7 @@ function removeFragment(id: string) {
89
90
  </UBadge>
90
91
  </div>
91
92
  <div v-else class="text-[11px] text-slate-500">
92
- None agents follow their default guidance.
93
+ {{ t('inspector.structure.bestPracticesEmpty') }}
93
94
  </div>
94
95
  </div>
95
96
  </div>