@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.
@@ -23,6 +23,7 @@ const board = useBoardStore()
23
23
  const brainstorm = useBrainstormStore()
24
24
  const ui = useUiStore()
25
25
  const toast = useToast()
26
+ const { t } = useI18n()
26
27
 
27
28
  const drafts = ref<Record<string, string>>({})
28
29
  const redoComment = ref('')
@@ -42,9 +43,15 @@ const { open, blockId, stage, close } = useResultView('brainstorm', {
42
43
  })
43
44
  const activeStage = computed<BrainstormStage>(() => stage.value ?? 'requirements')
44
45
  const isArchitecture = computed(() => activeStage.value === 'architecture')
45
- const subjectNoun = computed(() => (isArchitecture.value ? 'approach' : 'requirements'))
46
+ const subjectNoun = computed(() =>
47
+ isArchitecture.value
48
+ ? t('brainstorm.subjectNoun.architecture')
49
+ : t('brainstorm.subjectNoun.requirements'),
50
+ )
46
51
  const docNoun = computed(() =>
47
- isArchitecture.value ? 'technical approach' : 'requirements direction',
52
+ isArchitecture.value
53
+ ? t('brainstorm.docNoun.architecture')
54
+ : t('brainstorm.docNoun.requirements'),
48
55
  )
49
56
 
50
57
  const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
@@ -117,6 +124,28 @@ const STATUS_COLOR = {
117
124
  recommend_requested: 'primary',
118
125
  } as const satisfies Record<ReviewItemStatus, string>
119
126
 
127
+ // Exhaustive enum→label maps of literal keys (keeps the typed-key drift guard live vs a
128
+ // runtime-built `brainstorm.severity.${value}`).
129
+ const SEVERITY_LABELS: Record<ReviewItemSeverity, string> = {
130
+ low: 'brainstorm.severity.low',
131
+ medium: 'brainstorm.severity.medium',
132
+ high: 'brainstorm.severity.high',
133
+ }
134
+ const CATEGORY_LABELS: Record<ReviewItemCategory, string> = {
135
+ gap: 'brainstorm.category.gap',
136
+ clarification: 'brainstorm.category.clarification',
137
+ assumption: 'brainstorm.category.assumption',
138
+ risk: 'brainstorm.category.risk',
139
+ question: 'brainstorm.category.question',
140
+ }
141
+ const STATUS_LABELS: Record<ReviewItemStatus, string> = {
142
+ open: 'brainstorm.itemStatus.open',
143
+ answered: 'brainstorm.itemStatus.answered',
144
+ resolved: 'brainstorm.itemStatus.resolved',
145
+ dismissed: 'brainstorm.itemStatus.dismissed',
146
+ recommend_requested: 'brainstorm.itemStatus.recommend_requested',
147
+ }
148
+
120
149
  function notifyError(title: string, e: unknown) {
121
150
  toast.add({
122
151
  title,
@@ -134,7 +163,7 @@ async function submitReply(item: BrainstormItem) {
134
163
  await brainstorm.reply(session.value, item.id, text)
135
164
  drafts.value = { ...drafts.value, [item.id]: '' }
136
165
  } catch (e) {
137
- notifyError('Could not save the choice', e)
166
+ notifyError(t('brainstorm.toast.saveChoiceError'), e)
138
167
  }
139
168
  }
140
169
 
@@ -143,7 +172,7 @@ async function setStatus(item: BrainstormItem, itemStatus: BrainstormItemStatus)
143
172
  try {
144
173
  await brainstorm.setItemStatus(session.value, item.id, itemStatus)
145
174
  } catch (e) {
146
- notifyError('Could not update the option', e)
175
+ notifyError(t('brainstorm.toast.updateOptionError'), e)
147
176
  }
148
177
  }
149
178
 
@@ -152,14 +181,14 @@ async function incorporate(feedback?: string) {
152
181
  try {
153
182
  await brainstorm.incorporate(session.value, feedback)
154
183
  } catch (e) {
155
- notifyError('Could not incorporate the choices', e)
184
+ notifyError(t('brainstorm.toast.incorporateError'), e)
156
185
  return
157
186
  }
158
187
  redoComment.value = ''
159
188
  showRedo.value = false
160
189
  toast.add({
161
- title: `Drafting the ${docNoun.value} in the background`,
162
- description: "You're back on the board — we'll notify you only if more input is needed.",
190
+ title: t('brainstorm.toast.draftingTitle', { doc: docNoun.value }),
191
+ description: t('brainstorm.toast.draftingDescription'),
163
192
  icon: 'i-lucide-wand-sparkles',
164
193
  })
165
194
  close()
@@ -169,17 +198,18 @@ async function reReview() {
169
198
  if (!blockId.value) return
170
199
  try {
171
200
  const updated = await brainstorm.reReview(blockId.value, activeStage.value)
201
+ const newCount = brainstorm.openCount(updated)
172
202
  toast.add({
173
203
  title:
174
204
  updated.status === 'incorporated'
175
- ? 'Direction settled — continuing the pipeline'
205
+ ? t('brainstorm.toast.reReviewSettled')
176
206
  : updated.status === 'exceeded'
177
- ? 'Iteration limit reached — choose how to proceed'
178
- : `${brainstorm.openCount(updated)} new option(s) to react to`,
207
+ ? t('brainstorm.toast.reReviewExceeded')
208
+ : t('brainstorm.toast.reReviewNewOptions', { count: newCount }, newCount),
179
209
  icon: 'i-lucide-sparkles',
180
210
  })
181
211
  } catch (e) {
182
- notifyError('Could not re-run the brainstorm', e)
212
+ notifyError(t('brainstorm.toast.reReviewError'), e)
183
213
  }
184
214
  }
185
215
 
@@ -188,9 +218,9 @@ async function proceed() {
188
218
  acting.value = true
189
219
  try {
190
220
  await brainstorm.proceed(blockId.value, activeStage.value)
191
- toast.add({ title: 'Proceeding to the next phase', icon: 'i-lucide-arrow-right' })
221
+ toast.add({ title: t('brainstorm.toast.proceeding'), icon: 'i-lucide-arrow-right' })
192
222
  } catch (e) {
193
- notifyError('Could not proceed', e)
223
+ notifyError(t('brainstorm.toast.proceedError'), e)
194
224
  } finally {
195
225
  acting.value = false
196
226
  }
@@ -202,15 +232,15 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
202
232
  try {
203
233
  await brainstorm.resolveExceeded(blockId.value, activeStage.value, choice)
204
234
  if (choice === 'stop-reset') {
205
- toast.add({ title: 'Task reset — edit it and resubmit', icon: 'i-lucide-undo' })
235
+ toast.add({ title: t('brainstorm.toast.taskReset'), icon: 'i-lucide-undo' })
206
236
  close()
207
237
  } else if (choice === 'proceed') {
208
- toast.add({ title: 'Proceeding to the next phase', icon: 'i-lucide-arrow-right' })
238
+ toast.add({ title: t('brainstorm.toast.proceeding'), icon: 'i-lucide-arrow-right' })
209
239
  } else {
210
- toast.add({ title: 'One more brainstorm round granted', icon: 'i-lucide-rotate-cw' })
240
+ toast.add({ title: t('brainstorm.toast.extraRoundGranted'), icon: 'i-lucide-rotate-cw' })
211
241
  }
212
242
  } catch (e) {
213
- notifyError('Could not resolve the brainstorm', e)
243
+ notifyError(t('brainstorm.toast.resolveError'), e)
214
244
  } finally {
215
245
  acting.value = false
216
246
  }
@@ -237,13 +267,17 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
237
267
  </div>
238
268
  <div class="min-w-0">
239
269
  <h1 class="truncate text-base font-semibold text-white">
240
- {{ isArchitecture ? 'Architecture brainstorm' : 'Requirements brainstorm' }}
270
+ {{
271
+ isArchitecture
272
+ ? t('brainstorm.title.architecture')
273
+ : t('brainstorm.title.requirements')
274
+ }}
241
275
  </h1>
242
276
  <p v-if="block" class="truncate text-xs text-slate-500">{{ block.title }}</p>
243
277
  </div>
244
278
  <div class="ml-auto flex items-center gap-1.5">
245
279
  <UBadge v-if="session" color="neutral" variant="subtle" size="sm">
246
- Iteration {{ iteration }} / {{ maxIterations }}
280
+ {{ t('brainstorm.iteration', { current: iteration, max: maxIterations }) }}
247
281
  </UBadge>
248
282
  <UButton icon="i-lucide-x" color="neutral" variant="ghost" size="sm" @click="close" />
249
283
  </div>
@@ -253,10 +287,16 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
253
287
  <!-- main column -->
254
288
  <div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
255
289
  <p class="mb-4 text-sm text-slate-400">
256
- An AI partner proposed the {{ subjectNoun }} options below, each with its trade-offs.
257
- <span class="text-slate-300">Choose</span> the ones you want (and steer them) and
258
- <span class="text-slate-300">dismiss</span> the rest, then incorporate; it re-runs
259
- until you converge on a {{ docNoun }}.
290
+ <i18n-t keypath="brainstorm.intro" tag="span" scope="global">
291
+ <template #subject>{{ subjectNoun }}</template>
292
+ <template #doc>{{ docNoun }}</template>
293
+ <template #choose>
294
+ <span class="text-slate-300">{{ t('brainstorm.introChoose') }}</span>
295
+ </template>
296
+ <template #dismiss>
297
+ <span class="text-slate-300">{{ t('brainstorm.introDismiss') }}</span>
298
+ </template>
299
+ </i18n-t>
260
300
  </p>
261
301
 
262
302
  <!-- empty state -->
@@ -264,8 +304,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
264
304
  v-if="!session && !busy && !loading"
265
305
  class="rounded-lg border border-dashed border-slate-700 p-8 text-center text-sm text-slate-500"
266
306
  >
267
- No brainstorm yet. It runs automatically when this task's pipeline reaches the
268
- brainstorm step.
307
+ {{ t('brainstorm.empty') }}
269
308
  </div>
270
309
 
271
310
  <!-- working state (initial fetch on open, or an agent pass running) -->
@@ -274,7 +313,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
274
313
  class="flex items-center justify-center gap-2 p-8 text-sm text-slate-400"
275
314
  >
276
315
  <UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
277
- {{ loading && !busy ? 'Loading the brainstorm' : 'Generating options…' }}
316
+ {{ loading && !busy ? t('brainstorm.loading') : t('brainstorm.generating') }}
278
317
  </div>
279
318
 
280
319
  <template v-else-if="session">
@@ -284,15 +323,15 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
284
323
  class="mb-4 flex items-center gap-2 rounded-lg border border-emerald-900/60 bg-emerald-950/30 p-4 text-sm text-emerald-300"
285
324
  >
286
325
  <UIcon name="i-lucide-circle-check" class="h-5 w-5 shrink-0" />
287
- The {{ docNoun }} is settled. The document below is what the next stage uses.
326
+ {{ t('brainstorm.settledBanner', { doc: docNoun }) }}
288
327
  </div>
289
328
 
290
329
  <!-- iteration cap hit -->
291
330
  <IterationCapPrompt
292
331
  v-else-if="exceeded"
293
332
  class="mb-4"
294
- :heading="`Reached the ${maxIterations}-iteration limit with options still open.`"
295
- detail="Do one more brainstorm round, proceed to the next phase with the last direction anyway, or stop and reset the task so you can edit it and resubmit."
333
+ :heading="t('brainstorm.exceeded.heading', { max: maxIterations })"
334
+ :detail="t('brainstorm.exceeded.detail')"
296
335
  :loading="acting"
297
336
  @resolve="resolveExceeded"
298
337
  />
@@ -304,12 +343,10 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
304
343
  >
305
344
  <UIcon name="i-lucide-loader-circle" class="h-5 w-5 shrink-0 animate-spin" />
306
345
  <span v-if="incorporating">
307
- Folding your choices into a {{ docNoun }}… You can close this — we’ll notify you
308
- only if more input is needed.
346
+ {{ t('brainstorm.working.incorporating', { doc: docNoun }) }}
309
347
  </span>
310
348
  <span v-else>
311
- Re-running the brainstorm on the updated direction… You can close this — we’ll
312
- notify you only if more input is needed.
349
+ {{ t('brainstorm.working.reReviewing') }}
313
350
  </span>
314
351
  </div>
315
352
 
@@ -330,10 +367,10 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
330
367
  <div class="flex flex-wrap items-center gap-1.5">
331
368
  <span class="text-sm font-medium text-white">{{ item.title }}</span>
332
369
  <UBadge size="xs" variant="subtle" :color="SEVERITY_COLOR[item.severity]">
333
- {{ item.severity }}
370
+ {{ t(SEVERITY_LABELS[item.severity]) }}
334
371
  </UBadge>
335
372
  <UBadge size="xs" variant="outline" color="neutral">
336
- {{ item.category }}
373
+ {{ t(CATEGORY_LABELS[item.category]) }}
337
374
  </UBadge>
338
375
  <UBadge
339
376
  size="xs"
@@ -341,7 +378,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
341
378
  :color="STATUS_COLOR[item.status]"
342
379
  class="ml-auto"
343
380
  >
344
- {{ item.status }}
381
+ {{ t(STATUS_LABELS[item.status]) }}
345
382
  </UBadge>
346
383
  </div>
347
384
  <p class="mt-1 whitespace-pre-line text-sm text-slate-400">
@@ -354,7 +391,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
354
391
  class="mt-2 rounded-md border-l-2 border-slate-700 bg-slate-950/40 px-3 py-1.5 text-sm text-slate-300"
355
392
  >
356
393
  <span class="text-[10px] uppercase tracking-wide text-slate-500">
357
- Your choice
394
+ {{ t('brainstorm.yourChoice') }}
358
395
  </span>
359
396
  <p class="whitespace-pre-line">{{ item.reply }}</p>
360
397
  </div>
@@ -368,7 +405,9 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
368
405
  size="sm"
369
406
  class="mt-2 w-full"
370
407
  :placeholder="
371
- item.reply ? 'Refine your choice…' : 'Choose / steer this option…'
408
+ item.reply
409
+ ? t('brainstorm.replyPlaceholder.refine')
410
+ : t('brainstorm.replyPlaceholder.choose')
372
411
  "
373
412
  :disabled="frozen"
374
413
  />
@@ -381,7 +420,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
381
420
  :disabled="!(drafts[item.id] ?? '').trim() || frozen"
382
421
  @click="submitReply(item)"
383
422
  >
384
- Save choice
423
+ {{ t('brainstorm.saveChoice') }}
385
424
  </UButton>
386
425
  <UButton
387
426
  color="neutral"
@@ -391,7 +430,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
391
430
  :disabled="frozen"
392
431
  @click="setStatus(item, 'dismissed')"
393
432
  >
394
- Dismiss
433
+ {{ t('brainstorm.dismiss') }}
395
434
  </UButton>
396
435
  </div>
397
436
  </template>
@@ -406,7 +445,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
406
445
  :disabled="frozen"
407
446
  @click="setStatus(item, 'open')"
408
447
  >
409
- Reopen
448
+ {{ t('brainstorm.reopen') }}
410
449
  </UButton>
411
450
  </div>
412
451
  </div>
@@ -419,7 +458,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
419
458
  <div class="mb-3 flex items-center gap-1.5 text-[11px] text-emerald-400">
420
459
  <UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
421
460
  <span class="font-semibold uppercase tracking-wide">
422
- {{ incorporated ? docNoun : `${docNoun} (draft)` }}
461
+ {{ incorporated ? docNoun : t('brainstorm.docDraft', { doc: docNoun }) }}
423
462
  </span>
424
463
  </div>
425
464
  <div v-for="s in outline.sections" :key="s.id" class="mb-2">
@@ -454,19 +493,19 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
454
493
  <div class="flex flex-col gap-4 px-4 py-5">
455
494
  <div v-if="session" class="space-y-2 text-xs text-slate-400">
456
495
  <div class="flex items-center justify-between">
457
- <span>Options</span>
496
+ <span>{{ t('brainstorm.rail.options') }}</span>
458
497
  <span class="text-slate-300">{{ session.items.length }}</span>
459
498
  </div>
460
499
  <div class="flex items-center justify-between">
461
- <span>Open</span>
500
+ <span>{{ t('brainstorm.rail.open') }}</span>
462
501
  <span class="text-slate-300">{{ openCount }}</span>
463
502
  </div>
464
503
  <div class="flex items-center justify-between">
465
- <span>Chosen</span>
504
+ <span>{{ t('brainstorm.rail.chosen') }}</span>
466
505
  <span class="text-slate-300">{{ answeredCount }}</span>
467
506
  </div>
468
507
  <div v-if="session.model" class="flex items-center justify-between">
469
- <span>Model</span>
508
+ <span>{{ t('brainstorm.rail.model') }}</span>
470
509
  <span class="truncate pl-2 text-slate-500">{{ session.model }}</span>
471
510
  </div>
472
511
  </div>
@@ -485,7 +524,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
485
524
  :loading="acting"
486
525
  @click="proceed"
487
526
  >
488
- Proceed (nothing to incorporate)
527
+ {{ t('brainstorm.proceedNothing') }}
489
528
  </UButton>
490
529
  <UButton
491
530
  v-else
@@ -497,17 +536,16 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
497
536
  :disabled="!canIncorporate"
498
537
  @click="incorporate()"
499
538
  >
500
- Incorporate choices
539
+ {{ t('brainstorm.incorporateChoices') }}
501
540
  </UButton>
502
541
  <p class="text-[11px] leading-relaxed text-slate-500">
503
542
  <template v-if="canProceed">
504
- Every option is dismissed — proceed to the next phase without a direction.
543
+ {{ t('brainstorm.hint.allDismissed') }}
505
544
  </template>
506
545
  <template v-else-if="canIncorporate">
507
- Folds your choices into one {{ docNoun }}, then re-runs the brainstorm
508
- automatically.
546
+ {{ t('brainstorm.hint.incorporate', { doc: docNoun }) }}
509
547
  </template>
510
- <template v-else> Choose or dismiss every option to continue. </template>
548
+ <template v-else> {{ t('brainstorm.hint.chooseAll') }} </template>
511
549
  </p>
512
550
  </div>
513
551
 
@@ -521,7 +559,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
521
559
  :loading="busy"
522
560
  @click="reReview"
523
561
  >
524
- {{ busy ? 'Re-running…' : 'Looks good — re-run' }}
562
+ {{ busy ? t('brainstorm.reRunning') : t('brainstorm.reRun') }}
525
563
  </UButton>
526
564
  <UButton
527
565
  color="neutral"
@@ -531,7 +569,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
531
569
  icon="i-lucide-pencil"
532
570
  @click="showRedo = !showRedo"
533
571
  >
534
- Redo incorporation
572
+ {{ t('brainstorm.redoIncorporation') }}
535
573
  </UButton>
536
574
  <div v-if="showRedo" class="space-y-2">
537
575
  <UTextarea
@@ -540,7 +578,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
540
578
  autoresize
541
579
  size="sm"
542
580
  class="w-full"
543
- placeholder="What should the direction do differently?"
581
+ :placeholder="t('brainstorm.redoPlaceholder')"
544
582
  />
545
583
  <UButton
546
584
  color="primary"
@@ -552,12 +590,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
552
590
  :disabled="!redoComment.trim()"
553
591
  @click="incorporate(redoComment.trim())"
554
592
  >
555
- Redo with this direction
593
+ {{ t('brainstorm.redoWithDirection') }}
556
594
  </UButton>
557
595
  </div>
558
596
  <p class="text-[11px] leading-relaxed text-slate-500">
559
- Re-run runs the agent against this direction. If you’re unhappy with how it was
560
- merged, redo it with a comment instead.
597
+ {{ t('brainstorm.mergedHint') }}
561
598
  </p>
562
599
  </div>
563
600
 
@@ -565,7 +602,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
565
602
  v-if="session && incorporated"
566
603
  class="border-t border-slate-800 pt-4 text-[11px] leading-relaxed text-slate-500"
567
604
  >
568
- Direction settled — the pipeline is continuing with the document on the left.
605
+ {{ t('brainstorm.incorporatedFooter') }}
569
606
  </div>
570
607
  </div>
571
608
  </aside>