@cat-factory/app 0.46.10 → 0.46.12

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 (48) hide show
  1. package/app/components/auth/AuthGate.vue +2 -1
  2. package/app/components/auth/LoginScreen.vue +41 -31
  3. package/app/components/auth/ResetPasswordScreen.vue +16 -13
  4. package/app/components/auth/UserMenu.vue +3 -2
  5. package/app/components/layout/AccountDeploymentSettings.vue +75 -32
  6. package/app/components/layout/AccountFragmentSettings.vue +2 -3
  7. package/app/components/layout/AccountTeamSettings.vue +75 -45
  8. package/app/components/layout/AiProvidersBanner.vue +11 -11
  9. package/app/components/layout/BoardSwitcher.vue +63 -29
  10. package/app/components/layout/CommandBar.vue +68 -54
  11. package/app/components/layout/GitHubPatBanner.vue +13 -6
  12. package/app/components/layout/IntegrationBackTitle.vue +4 -1
  13. package/app/components/layout/IntegrationsHub.vue +67 -49
  14. package/app/components/layout/NotificationsInbox.vue +48 -19
  15. package/app/components/layout/PersonalSetupModal.vue +26 -16
  16. package/app/components/layout/ProviderConfigBanner.vue +14 -9
  17. package/app/components/layout/SideBar.vue +20 -18
  18. package/app/components/layout/SpendWarningBanner.vue +16 -16
  19. package/app/components/observability/StepMetricsBar.vue +31 -11
  20. package/app/components/observability/StepModelActivity.vue +3 -2
  21. package/app/components/panels/AgentStepDetail.vue +52 -39
  22. package/app/components/panels/DecisionModal.vue +12 -5
  23. package/app/components/panels/GenericStructuredResultView.vue +16 -6
  24. package/app/components/panels/InspectorPanel.vue +35 -28
  25. package/app/components/panels/ObservabilityPanel.vue +92 -49
  26. package/app/components/panels/StepMetadataCard.vue +87 -30
  27. package/app/components/panels/StepRestartControl.vue +4 -3
  28. package/app/components/panels/StepRunMeta.vue +23 -10
  29. package/app/components/panels/StepTestReport.vue +14 -11
  30. package/app/components/panels/inspector/ContainerSummary.vue +25 -14
  31. package/app/components/panels/inspector/EpicChildren.vue +7 -4
  32. package/app/components/panels/inspector/RecurringScheduleSettings.vue +60 -19
  33. package/app/components/panels/inspector/ServiceFragments.vue +3 -2
  34. package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +18 -13
  35. package/app/components/panels/inspector/ServiceTestConfig.vue +50 -39
  36. package/app/components/panels/inspector/TaskAgentConfig.vue +6 -5
  37. package/app/components/panels/inspector/TaskDependencies.vue +9 -4
  38. package/app/components/panels/inspector/TaskEstimateBadge.vue +14 -13
  39. package/app/components/panels/inspector/TaskExecution.vue +65 -31
  40. package/app/components/panels/inspector/TaskRunSettings.vue +93 -56
  41. package/app/components/panels/inspector/TaskStructure.vue +5 -4
  42. package/app/components/providers/PersonalSubscriptionSection.vue +1 -2
  43. package/i18n/locales/en.json +814 -0
  44. package/i18n/locales/es.json +811 -0
  45. package/i18n/locales/fr.json +811 -0
  46. package/i18n/locales/pl.json +811 -0
  47. package/i18n/locales/uk.json +811 -0
  48. package/package.json +1 -1
@@ -27,6 +27,7 @@ const execution = useExecutionStore()
27
27
  const board = useBoardStore()
28
28
  const models = useModelsStore()
29
29
  const workspace = useWorkspaceStore()
30
+ const { t } = useI18n()
30
31
 
31
32
  onMounted(() => models.ensureLoaded(workspace.workspaceId ?? undefined))
32
33
 
@@ -187,7 +188,7 @@ async function copyOutput() {
187
188
  >
188
189
  <div class="border-b border-slate-800 px-4 py-3">
189
190
  <div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
190
- Contents
191
+ {{ t('panels.stepDetail.contents') }}
191
192
  </div>
192
193
  </div>
193
194
  <nav class="flex-1 space-y-0.5 overflow-auto px-2 py-3">
@@ -200,7 +201,7 @@ async function copyOutput() {
200
201
  "
201
202
  @click="goTo('step-details')"
202
203
  >
203
- Details
204
+ {{ t('panels.stepDetail.details') }}
204
205
  </button>
205
206
  <button
206
207
  v-for="s in tocSections"
@@ -242,7 +243,7 @@ async function copyOutput() {
242
243
  class="mr-1"
243
244
  >
244
245
  <UIcon name="i-lucide-shield-check" class="mr-1 h-3 w-3" />
245
- Approval required
246
+ {{ t('panels.stepDetail.approvalRequired') }}
246
247
  </UBadge>
247
248
  <UBadge
248
249
  v-else-if="companionExceeded"
@@ -252,7 +253,7 @@ async function copyOutput() {
252
253
  class="mr-1"
253
254
  >
254
255
  <UIcon name="i-lucide-alert-triangle" class="mr-1 h-3 w-3" />
255
- Decision required
256
+ {{ t('panels.stepDetail.decisionRequired') }}
256
257
  </UBadge>
257
258
  <UButton
258
259
  v-if="outline.sections.length"
@@ -260,7 +261,11 @@ async function copyOutput() {
260
261
  color="neutral"
261
262
  variant="ghost"
262
263
  size="sm"
263
- :title="allCollapsed ? 'Expand all sections' : 'Collapse all sections'"
264
+ :title="
265
+ allCollapsed
266
+ ? t('panels.stepDetail.expandAll')
267
+ : t('panels.stepDetail.collapseAll')
268
+ "
264
269
  @click="setAll(!allCollapsed)"
265
270
  />
266
271
  <UButton
@@ -269,7 +274,7 @@ async function copyOutput() {
269
274
  color="neutral"
270
275
  variant="ghost"
271
276
  size="sm"
272
- title="Copy raw output"
277
+ :title="t('panels.stepDetail.copyRawOutput')"
273
278
  @click="copyOutput"
274
279
  />
275
280
  <!-- Restart the pipeline from this step (shared two-click confirm; resetting
@@ -286,7 +291,7 @@ async function copyOutput() {
286
291
  color="neutral"
287
292
  variant="ghost"
288
293
  size="sm"
289
- title="Close (Esc)"
294
+ :title="t('panels.stepDetail.closeEsc')"
290
295
  @click="close"
291
296
  />
292
297
  </div>
@@ -323,8 +328,14 @@ async function copyOutput() {
323
328
  (one more round / proceed with the current output / stop & reset) -->
324
329
  <IterationCapPrompt
325
330
  v-if="companionExceeded"
326
- :heading="`${agent.label} hit its ${step.companion?.maxAttempts}-attempt rework limit, still below the ${pctOf(latestVerdict?.threshold ?? 0)} bar.`"
327
- detail="Do one more automatic rework round, proceed to the next step accepting the current output, or stop and reset the task so you can edit the inputs and resubmit."
331
+ :heading="
332
+ t('panels.stepDetail.companionCapHeading', {
333
+ agent: agent.label,
334
+ attempts: step.companion?.maxAttempts,
335
+ threshold: pctOf(latestVerdict?.threshold ?? 0),
336
+ })
337
+ "
338
+ :detail="t('panels.stepDetail.companionCapDetail')"
328
339
  :loading="resolvingCap"
329
340
  @resolve="resolveCompanionCap"
330
341
  />
@@ -344,7 +355,9 @@ async function copyOutput() {
344
355
  @click="showProvisioning = !showProvisioning"
345
356
  >
346
357
  {{
347
- showProvisioning ? 'Hide infrastructure attempts' : 'Infrastructure attempts'
358
+ showProvisioning
359
+ ? t('panels.stepDetail.hideInfraAttempts')
360
+ : t('panels.stepDetail.infraAttempts')
348
361
  }}
349
362
  </UButton>
350
363
  <ProvisioningLogsDrawer
@@ -363,7 +376,9 @@ async function copyOutput() {
363
376
  <section v-if="editing" class="scroll-mt-4">
364
377
  <div class="mb-2 flex items-center gap-1.5 text-[11px] text-amber-400">
365
378
  <UIcon name="i-lucide-pencil" class="h-3.5 w-3.5" />
366
- <span class="font-semibold uppercase tracking-wide">Editing the conclusions</span>
379
+ <span class="font-semibold uppercase tracking-wide">{{
380
+ t('panels.stepDetail.editingConclusions')
381
+ }}</span>
367
382
  </div>
368
383
  <UTextarea
369
384
  v-model="draftProposal"
@@ -372,7 +387,7 @@ async function copyOutput() {
372
387
  size="sm"
373
388
  class="w-full"
374
389
  :ui="{ base: 'font-mono text-[12px] leading-relaxed' }"
375
- placeholder="Edit the agent's conclusions; your edits are saved when you approve…"
390
+ :placeholder="t('panels.stepDetail.editConclusionsPlaceholder')"
376
391
  />
377
392
  </section>
378
393
 
@@ -419,7 +434,7 @@ async function copyOutput() {
419
434
  v-else
420
435
  class="rounded-lg border border-dashed border-slate-800 py-6 text-center text-sm text-slate-500"
421
436
  >
422
- This agent produced no prose output.
437
+ {{ t('panels.stepDetail.noProseOutput') }}
423
438
  </p>
424
439
  </div>
425
440
  </div>
@@ -434,14 +449,14 @@ async function copyOutput() {
434
449
  >
435
450
  <div class="border-b border-slate-800 px-4 py-3">
436
451
  <div class="text-[11px] font-semibold uppercase tracking-wide text-amber-400">
437
- {{ editing ? 'Approve with corrections' : 'Review & approve' }}
438
- </div>
439
- <p class="mt-1 text-[12px] text-slate-400">
440
452
  {{
441
453
  editing
442
- ? 'Edit the conclusions on the left; your edits are saved when you approve.'
443
- : 'Click any block in the output to comment on it, or leave overall feedback below.'
454
+ ? t('panels.stepDetail.approveWithCorrections')
455
+ : t('panels.stepDetail.reviewAndApprove')
444
456
  }}
457
+ </div>
458
+ <p class="mt-1 text-[12px] text-slate-400">
459
+ {{ editing ? t('panels.stepDetail.editHint') : t('panels.stepDetail.reviewHint') }}
445
460
  </p>
446
461
  </div>
447
462
 
@@ -450,8 +465,7 @@ async function copyOutput() {
450
465
  v-if="editing"
451
466
  class="rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 text-[12px] leading-relaxed text-amber-200/90"
452
467
  >
453
- You're editing the conclusions directly. Manual edits can't be combined with per-block
454
- comments — approve to save them, or cancel to return to review.
468
+ {{ t('panels.stepDetail.editingNotice') }}
455
469
  </p>
456
470
  <template v-else>
457
471
  <!-- composer for the block the human just clicked -->
@@ -460,7 +474,7 @@ async function copyOutput() {
460
474
  class="rounded-lg border border-indigo-500/40 bg-indigo-500/5 p-3"
461
475
  >
462
476
  <div class="mb-1 text-[10px] uppercase tracking-wide text-indigo-300">
463
- Commenting on
477
+ {{ t('panels.stepDetail.commentingOn') }}
464
478
  </div>
465
479
  <pre
466
480
  class="mb-2 max-h-24 overflow-auto whitespace-pre-wrap rounded bg-slate-950/60 p-2 text-[11px] text-slate-300"
@@ -472,11 +486,11 @@ async function copyOutput() {
472
486
  autoresize
473
487
  size="sm"
474
488
  class="w-full"
475
- placeholder="Leave a comment on this block…"
489
+ :placeholder="t('panels.stepDetail.commentPlaceholder')"
476
490
  />
477
491
  <div class="mt-2 flex justify-end gap-2">
478
492
  <UButton color="neutral" variant="ghost" size="xs" @click="cancelDraft">
479
- Cancel
493
+ {{ t('common.cancel') }}
480
494
  </UButton>
481
495
  <UButton
482
496
  color="primary"
@@ -484,7 +498,7 @@ async function copyOutput() {
484
498
  :disabled="!draftBody.trim()"
485
499
  @click="addDraftComment"
486
500
  >
487
- Add comment
501
+ {{ t('panels.stepDetail.addComment') }}
488
502
  </UButton>
489
503
  </div>
490
504
  </div>
@@ -497,11 +511,11 @@ async function copyOutput() {
497
511
  >
498
512
  <div class="mb-1 flex items-start justify-between gap-2">
499
513
  <div class="text-[10px] uppercase tracking-wide text-slate-500">
500
- Comment {{ idx + 1 }}
514
+ {{ t('panels.stepDetail.commentN', { number: idx + 1 }) }}
501
515
  </div>
502
516
  <button
503
517
  class="text-slate-500 transition hover:text-rose-400"
504
- title="Remove comment"
518
+ :title="t('panels.stepDetail.removeComment')"
505
519
  @click="removeComment(idx)"
506
520
  >
507
521
  <UIcon name="i-lucide-x" class="h-3.5 w-3.5" />
@@ -518,7 +532,7 @@ async function copyOutput() {
518
532
  <label
519
533
  class="mb-1 block text-[11px] font-semibold uppercase tracking-wide text-slate-400"
520
534
  >
521
- Overall feedback / reject reason
535
+ {{ t('panels.stepDetail.overallFeedback') }}
522
536
  </label>
523
537
  <UTextarea
524
538
  v-model="feedback"
@@ -526,7 +540,7 @@ async function copyOutput() {
526
540
  autoresize
527
541
  size="sm"
528
542
  class="w-full"
529
- placeholder="Describe the changes the agent should make (optional if you left per-block comments)"
543
+ :placeholder="t('panels.stepDetail.overallFeedbackPlaceholder')"
530
544
  />
531
545
  </div>
532
546
  </template>
@@ -542,7 +556,7 @@ async function copyOutput() {
542
556
  :loading="submitting"
543
557
  @click="approveWithEdits"
544
558
  >
545
- Approve with these edits
559
+ {{ t('panels.stepDetail.approveWithEdits') }}
546
560
  </UButton>
547
561
  <UButton
548
562
  color="neutral"
@@ -552,7 +566,7 @@ async function copyOutput() {
552
566
  :disabled="submitting"
553
567
  @click="cancelEditing"
554
568
  >
555
- Cancel edits
569
+ {{ t('panels.stepDetail.cancelEdits') }}
556
570
  </UButton>
557
571
  </div>
558
572
 
@@ -567,7 +581,7 @@ async function copyOutput() {
567
581
  :loading="submitting"
568
582
  @click="approve"
569
583
  >
570
- Approve &amp; proceed
584
+ {{ t('panels.stepDetail.approveAndProceed') }}
571
585
  </UButton>
572
586
  <UButton
573
587
  color="primary"
@@ -578,7 +592,7 @@ async function copyOutput() {
578
592
  :disabled="rejectArmed || submitting"
579
593
  @click="startEditing"
580
594
  >
581
- Approve with corrections
595
+ {{ t('panels.stepDetail.approveWithCorrections') }}
582
596
  </UButton>
583
597
 
584
598
  <!-- destructive: a two-step inline confirm instead of a native dialog -->
@@ -587,7 +601,7 @@ async function copyOutput() {
587
601
  class="rounded-lg border border-rose-500/40 bg-rose-500/5 p-2.5"
588
602
  >
589
603
  <p class="mb-2 text-[11px] text-rose-200">
590
- Reject this proposal and stop the run entirely?
604
+ {{ t('panels.stepDetail.rejectConfirmPrompt') }}
591
605
  </p>
592
606
  <div class="flex gap-2">
593
607
  <UButton
@@ -598,7 +612,7 @@ async function copyOutput() {
598
612
  :disabled="submitting"
599
613
  @click="disarmReject"
600
614
  >
601
- Cancel
615
+ {{ t('common.cancel') }}
602
616
  </UButton>
603
617
  <UButton
604
618
  color="error"
@@ -608,7 +622,7 @@ async function copyOutput() {
608
622
  :loading="submitting"
609
623
  @click="reject"
610
624
  >
611
- Confirm reject
625
+ {{ t('panels.stepDetail.confirmReject') }}
612
626
  </UButton>
613
627
  </div>
614
628
  </div>
@@ -623,7 +637,7 @@ async function copyOutput() {
623
637
  :loading="submitting"
624
638
  @click="requestChanges"
625
639
  >
626
- Request changes
640
+ {{ t('panels.stepDetail.requestChanges') }}
627
641
  </UButton>
628
642
  <UButton
629
643
  color="error"
@@ -634,12 +648,11 @@ async function copyOutput() {
634
648
  :disabled="submitting"
635
649
  @click="armReject"
636
650
  >
637
- Reject
651
+ {{ t('panels.stepDetail.reject') }}
638
652
  </UButton>
639
653
  </div>
640
654
  <p class="text-[10px] text-slate-500">
641
- Request changes re-runs this step with your feedback &amp; comments. Reject stops the
642
- run entirely.
655
+ {{ t('panels.stepDetail.requestChangesHint') }}
643
656
  </p>
644
657
  </div>
645
658
  </aside>
@@ -4,6 +4,7 @@ import { agentKindMeta } from '~/utils/catalog'
4
4
  const execution = useExecutionStore()
5
5
  const board = useBoardStore()
6
6
  const ui = useUiStore()
7
+ const { t } = useI18n()
7
8
 
8
9
  const ctx = computed(() => ui.decisionContext)
9
10
 
@@ -30,7 +31,7 @@ function choose(option: string) {
30
31
  </script>
31
32
 
32
33
  <template>
33
- <UModal v-model:open="open" title="Decision required">
34
+ <UModal v-model:open="open" :title="t('panels.decision.title')">
34
35
  <template #body>
35
36
  <div v-if="decision && agent" class="space-y-4" data-testid="decision-modal">
36
37
  <div class="flex items-center gap-2 text-sm text-slate-400">
@@ -41,9 +42,15 @@ function choose(option: string) {
41
42
  <UIcon :name="agent.icon" class="h-4 w-4" :style="{ color: agent.color }" />
42
43
  </div>
43
44
  <div>
44
- <span class="font-medium text-slate-200">{{ agent.label }}</span>
45
- <span v-if="block"> on </span>
46
- <span v-if="block" class="font-medium text-slate-200">{{ block.title }}</span>
45
+ <i18n-t v-if="block" keypath="panels.decision.agentOnBlock" tag="span" scope="global">
46
+ <template #agent>
47
+ <span class="font-medium text-slate-200">{{ agent.label }}</span>
48
+ </template>
49
+ <template #block>
50
+ <span class="font-medium text-slate-200">{{ block.title }}</span>
51
+ </template>
52
+ </i18n-t>
53
+ <span v-else class="font-medium text-slate-200">{{ agent.label }}</span>
47
54
  </div>
48
55
  </div>
49
56
 
@@ -64,7 +71,7 @@ function choose(option: string) {
64
71
  </UButton>
65
72
  </div>
66
73
  <p class="text-[11px] text-slate-500">
67
- This is a visualization — any choice simply resumes the pipeline.
74
+ {{ t('panels.decision.visualizationHint') }}
68
75
  </p>
69
76
  </div>
70
77
  </template>
@@ -13,6 +13,7 @@ import StepRestartControl from '~/components/panels/StepRestartControl.vue'
13
13
  const board = useBoardStore()
14
14
  const execution = useExecutionStore()
15
15
  const agents = useAgentsStore()
16
+ const { t } = useI18n()
16
17
 
17
18
  // Shared seam contract (open/blockId/close + Escape). No `onOpen` loader: this window reads
18
19
  // its data straight off the execution step, so there's nothing to fetch on open.
@@ -28,6 +29,16 @@ const step = computed(() => {
28
29
  })
29
30
  const meta = computed(() => (step.value ? agents.get(step.value.agentKind) : undefined))
30
31
 
32
+ const headerLabel = computed(() => meta.value?.label ?? t('panels.structuredResult.fallbackTitle'))
33
+ const headerTitle = computed(() =>
34
+ block.value
35
+ ? t('panels.structuredResult.titleWithBlock', {
36
+ label: headerLabel.value,
37
+ title: block.value.title,
38
+ })
39
+ : headerLabel.value,
40
+ )
41
+
31
42
  /** The agent's structured JSON, pretty-printed; null when the step produced none. */
32
43
  const customJson = computed<string | null>(() => {
33
44
  const custom = step.value?.custom
@@ -59,10 +70,10 @@ const customJson = computed<string | null>(() => {
59
70
  </span>
60
71
  <div class="min-w-0 flex-1">
61
72
  <h2 class="truncate text-sm font-semibold text-slate-100">
62
- {{ meta?.label ?? 'Agent result' }}{{ block ? ` — ${block.title}` : '' }}
73
+ {{ headerTitle }}
63
74
  </h2>
64
75
  <p class="truncate text-[11px] text-slate-400">
65
- {{ meta?.description ?? 'Structured agent output' }}
76
+ {{ meta?.description ?? t('panels.structuredResult.fallbackDescription') }}
66
77
  </p>
67
78
  </div>
68
79
  <StepRestartControl
@@ -90,7 +101,7 @@ const customJson = computed<string | null>(() => {
90
101
 
91
102
  <template v-if="customJson">
92
103
  <h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
93
- Structured output
104
+ {{ t('panels.structuredResult.structuredOutput') }}
94
105
  </h3>
95
106
  <pre
96
107
  class="overflow-x-auto rounded-lg border border-slate-800 bg-slate-950/60 p-3 text-[12px] leading-relaxed text-slate-200"
@@ -102,10 +113,9 @@ const customJson = computed<string | null>(() => {
102
113
  class="flex h-full flex-col items-center justify-center gap-2 text-center text-slate-400"
103
114
  >
104
115
  <UIcon name="i-lucide-braces" class="h-8 w-8 opacity-40" />
105
- <p class="text-sm">No result yet.</p>
116
+ <p class="text-sm">{{ t('panels.structuredResult.noResult') }}</p>
106
117
  <p class="max-w-sm text-[11px] text-slate-500">
107
- The structured output appears once this agent finishes. While it runs, the step
108
- shows live progress on the board.
118
+ {{ t('panels.structuredResult.noResultHint') }}
109
119
  </p>
110
120
  </div>
111
121
  </div>
@@ -28,6 +28,7 @@ const agentRuns = useAgentRunsStore()
28
28
  const github = useGitHubStore()
29
29
  const recurring = useRecurringPipelinesStore()
30
30
  const requirements = useRequirementsStore()
31
+ const { t } = useI18n()
31
32
 
32
33
  // When the selected task block backs a recurring pipeline, the inspector shows the
33
34
  // schedule controls + history, and "Delete" removes the schedule (block + history).
@@ -58,20 +59,20 @@ const instance = computed(() => execution.getInstance(block.value?.executionId))
58
59
  const typeMeta = computed(() => (block.value ? blockTypeMeta(block.value.type) : null))
59
60
 
60
61
  // Containers show a derived activity status (never "done"); tasks use their own.
61
- const FRAME_LABEL: Record<BlockStatus, string> = {
62
- planned: 'No tasks',
63
- ready: 'Live',
64
- in_progress: 'Active',
65
- blocked: 'Needs attention',
66
- pr_ready: 'Active',
67
- done: 'Live',
62
+ const FRAME_LABEL_KEYS: Record<BlockStatus, string> = {
63
+ planned: 'panels.inspector.frameStatus.planned',
64
+ ready: 'panels.inspector.frameStatus.ready',
65
+ in_progress: 'panels.inspector.frameStatus.in_progress',
66
+ blocked: 'panels.inspector.frameStatus.blocked',
67
+ pr_ready: 'panels.inspector.frameStatus.pr_ready',
68
+ done: 'panels.inspector.frameStatus.done',
68
69
  }
69
70
  const effectiveStatus = computed<BlockStatus>(() =>
70
71
  isContainer.value ? board.frameStatus(block.value!.id) : block.value!.status,
71
72
  )
72
73
  const statusMeta = computed(() => (block.value ? STATUS_META[effectiveStatus.value] : null))
73
74
  const statusLabel = computed(() =>
74
- isContainer.value ? FRAME_LABEL[effectiveStatus.value] : statusMeta.value!.label,
75
+ isContainer.value ? t(FRAME_LABEL_KEYS[effectiveStatus.value]) : statusMeta.value!.label,
75
76
  )
76
77
 
77
78
  const runnable = computed(() => (block.value ? board.isRunnable(block.value.id) : false))
@@ -80,12 +81,12 @@ const runnable = computed(() => (block.value ? board.isRunnable(block.value.id)
80
81
  // reads as "Delete task" rather than ambiguously removing the whole service.
81
82
  const deleteLabel = computed(() =>
82
83
  schedule.value
83
- ? 'Delete recurring pipeline'
84
+ ? t('panels.inspector.deleteRecurringPipeline')
84
85
  : isTask.value
85
- ? 'Delete task'
86
+ ? t('panels.inspector.deleteTask')
86
87
  : level.value === 'module'
87
- ? 'Delete module'
88
- : 'Delete service',
88
+ ? t('panels.inspector.deleteModule')
89
+ : t('panels.inspector.deleteService'),
89
90
  )
90
91
 
91
92
  // A task is "started" once a pipeline has been launched on it (it has an
@@ -252,7 +253,7 @@ const showOriginalDescription = ref(false)
252
253
  v-model="block.title"
253
254
  size="sm"
254
255
  class="w-full"
255
- placeholder="Title…"
256
+ :placeholder="t('panels.inspector.titlePlaceholder')"
256
257
  @change="saveTitle"
257
258
  @blur="saveTitle"
258
259
  />
@@ -265,13 +266,15 @@ const showOriginalDescription = ref(false)
265
266
  class="mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-emerald-400"
266
267
  >
267
268
  <UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
268
- Reworked requirements
269
+ {{ t('panels.inspector.reworkedRequirements') }}
269
270
  </div>
270
271
  <p class="line-clamp-5 whitespace-pre-line text-[13px] leading-relaxed text-slate-300">
271
272
  {{ reqReworkedText }}
272
273
  </p>
273
274
  <div class="mt-2 flex items-center justify-between gap-2">
274
- <p class="text-[11px] text-slate-500">Agent steps use this document.</p>
275
+ <p class="text-[11px] text-slate-500">
276
+ {{ t('panels.inspector.agentStepsUseDocument') }}
277
+ </p>
275
278
  <UButton
276
279
  color="neutral"
277
280
  variant="link"
@@ -279,7 +282,7 @@ const showOriginalDescription = ref(false)
279
282
  icon="i-lucide-maximize-2"
280
283
  @click="ui.openRequirementReview(block.id)"
281
284
  >
282
- Open
285
+ {{ t('panels.inspector.open') }}
283
286
  </UButton>
284
287
  </div>
285
288
  </div>
@@ -290,7 +293,7 @@ const showOriginalDescription = ref(false)
290
293
  :icon="showOriginalDescription ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
291
294
  @click="showOriginalDescription = !showOriginalDescription"
292
295
  >
293
- Original description (frozen)
296
+ {{ t('panels.inspector.originalDescriptionFrozen') }}
294
297
  </UButton>
295
298
  <UTextarea
296
299
  v-if="showOriginalDescription"
@@ -300,7 +303,7 @@ const showOriginalDescription = ref(false)
300
303
  size="sm"
301
304
  class="w-full"
302
305
  disabled
303
- placeholder="No description"
306
+ :placeholder="t('panels.inspector.noDescription')"
304
307
  />
305
308
  </template>
306
309
 
@@ -313,13 +316,13 @@ const showOriginalDescription = ref(false)
313
316
  size="sm"
314
317
  class="w-full"
315
318
  :disabled="!editable"
316
- placeholder="Describe this block…"
319
+ :placeholder="t('panels.inspector.describeBlock')"
317
320
  @change="saveDescription"
318
321
  @blur="saveDescription"
319
322
  />
320
323
  <p v-if="isTask && !editable" class="flex items-center gap-1 text-[11px] text-slate-500">
321
324
  <UIcon name="i-lucide-lock" class="h-3 w-3" />
322
- This task has started — its details are locked.
325
+ {{ t('panels.inspector.taskStartedLocked') }}
323
326
  </p>
324
327
 
325
328
  <!-- prior incorporated requirements kept as a base after a review-driven reset -->
@@ -328,13 +331,13 @@ const showOriginalDescription = ref(false)
328
331
  class="mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-400"
329
332
  >
330
333
  <UIcon name="i-lucide-history" class="h-3.5 w-3.5" />
331
- Last incorporated requirements
334
+ {{ t('panels.inspector.lastIncorporatedRequirements') }}
332
335
  </div>
333
336
  <p class="line-clamp-5 whitespace-pre-line text-[13px] leading-relaxed text-slate-300">
334
337
  {{ reqReworkedText }}
335
338
  </p>
336
339
  <p class="mt-2 text-[11px] text-slate-500">
337
- From the previous review. Use it as a base — edit the description above and resubmit.
340
+ {{ t('panels.inspector.priorDocHint') }}
338
341
  </p>
339
342
  </div>
340
343
  </template>
@@ -350,7 +353,7 @@ const showOriginalDescription = ref(false)
350
353
  >
351
354
  <span class="flex items-center gap-1.5 text-xs text-amber-300">
352
355
  <UIcon name="i-lucide-loader-circle" class="h-3.5 w-3.5 animate-spin" />
353
- Bootstrapping…
356
+ {{ t('panels.inspector.bootstrapping') }}
354
357
  </span>
355
358
  <AgentStopButton :run-id="runningRun.runId" :kind="runningRun.kind" size="xs" />
356
359
  </div>
@@ -391,7 +394,11 @@ const showOriginalDescription = ref(false)
391
394
  icon="i-lucide-ticket"
392
395
  @click="ui.openTaskImport()"
393
396
  >
394
- {{ tasks.anyOffered ? 'Import issue' : 'Connect a tracker' }}
397
+ {{
398
+ tasks.anyOffered
399
+ ? t('panels.inspector.importIssue')
400
+ : t('panels.inspector.connectTracker')
401
+ }}
395
402
  </UButton>
396
403
  <UButton
397
404
  v-if="isContainer && documents.available && documents.anyConnected"
@@ -401,7 +408,7 @@ const showOriginalDescription = ref(false)
401
408
  icon="i-lucide-wand-sparkles"
402
409
  @click="spawnFromDocument"
403
410
  >
404
- Spawn from document
411
+ {{ t('panels.inspector.spawnFromDocument') }}
405
412
  </UButton>
406
413
  </div>
407
414
 
@@ -422,7 +429,7 @@ const showOriginalDescription = ref(false)
422
429
  icon="i-lucide-scroll-text"
423
430
  @click="ui.openServiceSpec(block.id)"
424
431
  >
425
- View Requirements
432
+ {{ t('panels.inspector.viewRequirements') }}
426
433
  </UButton>
427
434
 
428
435
  <!-- service / module: tasks summary -->
@@ -461,7 +468,7 @@ const showOriginalDescription = ref(false)
461
468
  trailing-icon="i-lucide-chevron-down"
462
469
  :disabled="!runnable"
463
470
  >
464
- {{ instance ? 'Re-run' : 'Run' }}
471
+ {{ instance ? t('panels.inspector.reRun') : t('panels.inspector.run') }}
465
472
  </UButton>
466
473
  </UDropdownMenu>
467
474
  <UButton
@@ -472,7 +479,7 @@ const showOriginalDescription = ref(false)
472
479
  icon="i-lucide-maximize-2"
473
480
  @click="ui.focus(block.id)"
474
481
  >
475
- Focus
482
+ {{ t('panels.inspector.focus') }}
476
483
  </UButton>
477
484
  <UButton
478
485
  color="error"