@cat-factory/app 0.311.2 → 0.312.0

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 (210) hide show
  1. package/README.md +92 -4
  2. package/app/app.config.ts +7 -0
  3. package/app/components/assistant/AssistantModal.vue +2 -5
  4. package/app/components/auth/AuthGate.vue +2 -1
  5. package/app/components/binaryCandidates/BinaryCandidatesWindow.vue +14 -15
  6. package/app/components/binaryOutput/StoredAssetView.vue +1 -3
  7. package/app/components/board/AddTaskModal.vue +3 -10
  8. package/app/components/board/AgentFailureCard.vue +4 -5
  9. package/app/components/board/AgentStopButton.vue +5 -7
  10. package/app/components/board/BoardCanvas.vue +31 -30
  11. package/app/components/board/CreateInitiativeModal.vue +2 -4
  12. package/app/components/board/RecurringPipelineModal.vue +2 -7
  13. package/app/components/board/nodes/AgentChip.vue +3 -2
  14. package/app/components/board/nodes/BlockNode.vue +14 -11
  15. package/app/components/board/nodes/InitiativeCard.vue +6 -3
  16. package/app/components/board/nodes/TaskCard.vue +45 -212
  17. package/app/components/bootstrap/AdoptionReviewModal.vue +2 -5
  18. package/app/components/bootstrap/BootstrapModal.vue +6 -15
  19. package/app/components/bootstrap/BootstrapRunSteps.vue +3 -1
  20. package/app/components/brainstorm/BrainstormWindow.vue +32 -26
  21. package/app/components/bugFishing/BugFishingWindow.vue +15 -13
  22. package/app/components/clarity/ClarityReviewWindow.vue +31 -26
  23. package/app/components/common/ClarificationItem.vue +3 -1
  24. package/app/components/common/EmptyState.vue +41 -19
  25. package/app/components/common/InterviewGateNotice.vue +3 -1
  26. package/app/components/common/Spinner.vue +28 -0
  27. package/app/components/consensus/ConsensusSessionWindow.vue +5 -5
  28. package/app/components/docs/DocInterviewWindow.vue +6 -6
  29. package/app/components/documents/ContextDocumentPicker.vue +2 -2
  30. package/app/components/documents/DocumentImportModal.vue +9 -8
  31. package/app/components/documents/DocumentSourceConnectModal.vue +10 -10
  32. package/app/components/documents/DocumentTemplatesModal.vue +13 -7
  33. package/app/components/documents/RepoContextDocPicker.vue +2 -2
  34. package/app/components/documents/SpawnPreviewModal.vue +4 -6
  35. package/app/components/documents/StartFromDesignModal.vue +2 -1
  36. package/app/components/documents/TaskContextDocs.vue +4 -5
  37. package/app/components/environments/EnvironmentStatusPanel.vue +11 -8
  38. package/app/components/environments/steps/EnvPickStep.vue +7 -3
  39. package/app/components/environments/steps/EnvPreflightStep.vue +6 -5
  40. package/app/components/focus/BlockFocusView.vue +6 -5
  41. package/app/components/followUp/FollowUpWindow.vue +7 -9
  42. package/app/components/forkDecision/ForkDecisionWindow.vue +9 -8
  43. package/app/components/foundational/FoundationalServiceCatalogList.vue +9 -5
  44. package/app/components/foundational/FoundationalServiceRegistry.vue +11 -7
  45. package/app/components/foundational/FoundationalServiceSources.vue +20 -22
  46. package/app/components/foundational/FoundationalSuppressions.vue +2 -2
  47. package/app/components/foundational/ServiceCatalogConnection.vue +15 -15
  48. package/app/components/fragments/FragmentLibraryManager.vue +36 -34
  49. package/app/components/fragments/FragmentSelector.vue +7 -3
  50. package/app/components/gates/GateResultView.vue +6 -9
  51. package/app/components/github/AddServiceFromRepoModal.vue +6 -10
  52. package/app/components/github/GitHubConnect.vue +10 -17
  53. package/app/components/github/GitHubPanel.vue +44 -47
  54. package/app/components/github/RepoTreeBrowser.vue +9 -5
  55. package/app/components/guidedReview/GuidedReviewModal.vue +16 -6
  56. package/app/components/guidedReview/GuidedReviewOverview.vue +2 -1
  57. package/app/components/guidedReview/GuidedReviewPicker.vue +10 -7
  58. package/app/components/guidedReview/GuidedReviewThread.vue +9 -4
  59. package/app/components/humanTest/HumanTestWindow.vue +2 -1
  60. package/app/components/initiative/InitiativePlanningWindow.vue +6 -6
  61. package/app/components/initiative/InitiativeTrackerWindow.vue +11 -11
  62. package/app/components/judge/JudgeResultView.vue +2 -7
  63. package/app/components/kaizen/KaizenPanel.vue +20 -5
  64. package/app/components/kaizen/KaizenStepStatus.vue +2 -1
  65. package/app/components/layout/AccountAuditLog.vue +11 -3
  66. package/app/components/layout/AccountDeploymentSettings.vue +17 -62
  67. package/app/components/layout/AccountFailureKindRules.vue +6 -5
  68. package/app/components/layout/AccountModelPolicySettings.vue +2 -6
  69. package/app/components/layout/AccountPlatformAlertSettings.vue +5 -13
  70. package/app/components/layout/AccountRiskPolicySettings.vue +13 -14
  71. package/app/components/layout/AccountRunCredentialSettings.vue +2 -2
  72. package/app/components/layout/AccountTeamSettings.vue +12 -9
  73. package/app/components/layout/BoardSwitcher.vue +2 -2
  74. package/app/components/layout/BoardToolbar.vue +27 -37
  75. package/app/components/layout/BoardTopOverlays.vue +8 -2
  76. package/app/components/layout/CommandBar.vue +7 -3
  77. package/app/components/layout/ConnectionStatusBanner.vue +2 -1
  78. package/app/components/layout/IntegrationsHub.vue +7 -3
  79. package/app/components/layout/ModelProvidersHub.vue +7 -3
  80. package/app/components/layout/NotificationsInbox.vue +3 -11
  81. package/app/components/layout/SideBar.vue +5 -0
  82. package/app/components/layout/WorkViewSwitcher.vue +64 -0
  83. package/app/components/layout/WorkspaceMembersSettings.vue +12 -5
  84. package/app/components/media/ArtifactLightbox.vue +4 -3
  85. package/app/components/notifications/NotificationSettingsPanel.vue +4 -11
  86. package/app/components/observability/ToolCallList.vue +12 -17
  87. package/app/components/panels/AgentStepDetail.vue +5 -5
  88. package/app/components/panels/GenericStructuredResultView.vue +6 -9
  89. package/app/components/panels/InspectorPanel.vue +2 -1
  90. package/app/components/panels/MergerResultView.vue +5 -6
  91. package/app/components/panels/ObservabilityPanel.vue +26 -41
  92. package/app/components/panels/OperatorDashboardPanel.vue +29 -17
  93. package/app/components/panels/ReportsPanel.vue +17 -11
  94. package/app/components/panels/ReportsSpendBreakdown.vue +7 -3
  95. package/app/components/panels/RunDetailLoadState.vue +4 -3
  96. package/app/components/panels/StepContainerStatus.vue +4 -3
  97. package/app/components/panels/StepDelegatedStatus.vue +3 -2
  98. package/app/components/panels/StepMetadataCard.vue +6 -8
  99. package/app/components/panels/StepRunMeta.vue +2 -5
  100. package/app/components/panels/inspector/ContainerSummary.vue +7 -1
  101. package/app/components/panels/inspector/EpicChildren.vue +7 -3
  102. package/app/components/panels/inspector/FrontendConfig.vue +7 -3
  103. package/app/components/panels/inspector/InitiativeInspector.vue +5 -2
  104. package/app/components/panels/inspector/ServiceConnections.vue +7 -3
  105. package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +2 -6
  106. package/app/components/panels/inspector/ServiceTestSecrets.vue +2 -6
  107. package/app/components/panels/inspector/ServiceTestingContext.vue +2 -6
  108. package/app/components/panels/inspector/ServiceValidationConfig.vue +38 -39
  109. package/app/components/panels/inspector/TaskExecution.vue +5 -6
  110. package/app/components/panels/inspector/TaskRunSettings.vue +7 -3
  111. package/app/components/panels/inspector/TaskStructure.vue +0 -1
  112. package/app/components/pipeline/AgentPromptEditor.vue +25 -18
  113. package/app/components/pipeline/PipelineBuilder.vue +14 -24
  114. package/app/components/pipeline/PipelineProgress.vue +16 -17
  115. package/app/components/prReview/PrReviewPhaseBadge.vue +3 -2
  116. package/app/components/prReview/PrReviewWindow.vue +16 -20
  117. package/app/components/providers/ApiKeysSection.vue +2 -6
  118. package/app/components/providers/PersonalCredentialModal.vue +2 -6
  119. package/app/components/providers/PersonalSubscriptionSection.vue +3 -7
  120. package/app/components/providers/VendorCredentialsModal.vue +2 -6
  121. package/app/components/provisioning/ProvisioningLogsDrawer.vue +10 -6
  122. package/app/components/queue/QueueCard.vue +269 -0
  123. package/app/components/queue/QueueView.vue +192 -0
  124. package/app/components/ralph/RalphLoopResultView.vue +2 -7
  125. package/app/components/requirements/RequirementsReviewWindow.vue +44 -39
  126. package/app/components/sandbox/SandboxPanel.vue +37 -25
  127. package/app/components/settings/AccountSettingsPanel.vue +6 -3
  128. package/app/components/settings/ApiTokensPanel.vue +2 -6
  129. package/app/components/settings/BudgetSettings.vue +2 -2
  130. package/app/components/settings/CapabilityCredentialsPanel.vue +2 -6
  131. package/app/components/settings/CloudflareHandlerSection.vue +3 -7
  132. package/app/components/settings/ConsensusGroupsSection.vue +7 -3
  133. package/app/components/settings/CustomManifestTypeEditor.vue +7 -3
  134. package/app/components/settings/DefaultProvisionTypeSection.vue +2 -2
  135. package/app/components/settings/InfraHandlersConfigurator.vue +8 -11
  136. package/app/components/settings/IssueTrackerPanel.vue +9 -9
  137. package/app/components/settings/LocalContainerPoolSettings.vue +2 -6
  138. package/app/components/settings/LocalModelEndpointsPanel.vue +5 -7
  139. package/app/components/settings/McpAuthorizeScreen.vue +8 -3
  140. package/app/components/settings/McpOAuthCallbackScreen.vue +2 -1
  141. package/app/components/settings/ModelConfigurationPanel.vue +45 -17
  142. package/app/components/settings/ObservabilityConnectionPanel.vue +4 -15
  143. package/app/components/settings/OpenRouterCatalogPanel.vue +8 -24
  144. package/app/components/settings/PackageRegistriesPanel.vue +2 -6
  145. package/app/components/settings/ProviderConnectionTab.vue +3 -7
  146. package/app/components/settings/RiskPolicyPanel.vue +4 -16
  147. package/app/components/settings/SharedStacksPanel.vue +8 -18
  148. package/app/components/settings/TaskTypeSuppressionsPanel.vue +10 -6
  149. package/app/components/settings/UsageSettings.vue +11 -2
  150. package/app/components/settings/UserSecretsSection.vue +5 -7
  151. package/app/components/settings/WorkspaceMetadataSettings.vue +9 -9
  152. package/app/components/settings/WorkspaceSettingsPanel.vue +3 -11
  153. package/app/components/skills/ReviewSkillQueue.vue +7 -3
  154. package/app/components/skills/SkillLibraryManager.vue +20 -18
  155. package/app/components/slack/SlackPanel.vue +5 -8
  156. package/app/components/spec/ServiceSpecWindow.vue +32 -29
  157. package/app/components/tasks/BugHuntModal.vue +23 -19
  158. package/app/components/tasks/ContextIssuePicker.vue +2 -2
  159. package/app/components/tasks/TaskContextIssues.vue +4 -5
  160. package/app/components/tasks/TaskImportModal.vue +10 -8
  161. package/app/components/tasks/TaskSourceConnectModal.vue +11 -11
  162. package/app/components/testing/TestReportWindow.vue +6 -12
  163. package/app/components/tutorial/TutorialCatalogue.vue +7 -3
  164. package/app/components/tutorial/TutorialOverlay.vue +2 -1
  165. package/app/components/tutorial/TutorialPrompt.vue +7 -3
  166. package/app/components/vcs/GitLabConnect.vue +2 -6
  167. package/app/components/visualConfirm/VisualConfirmationWindow.vue +9 -4
  168. package/app/composables/toastTitleKeys.spec.ts +143 -0
  169. package/app/composables/useActionToast.spec.ts +71 -0
  170. package/app/composables/useActionToast.ts +101 -0
  171. package/app/composables/useBlockDeletion.ts +3 -6
  172. package/app/composables/useConfirmAction.ts +5 -3
  173. package/app/composables/useContextLinking.spec.ts +3 -0
  174. package/app/composables/useContextLinking.ts +11 -16
  175. package/app/composables/useCopyToClipboard.ts +4 -3
  176. package/app/composables/useFrameLanes.ts +6 -82
  177. package/app/composables/useNavContributions.ts +6 -5
  178. package/app/composables/usePipelineErrorToast.spec.ts +24 -0
  179. package/app/composables/usePipelineErrorToast.ts +26 -2
  180. package/app/composables/usePipelineLibraryActions.ts +8 -9
  181. package/app/composables/useTaskActions.ts +254 -0
  182. package/app/composables/useTaskLaneClassifier.ts +88 -0
  183. package/app/modular/nav-contributions.spec.ts +4 -0
  184. package/app/modular/nav-contributions.ts +31 -0
  185. package/app/modular/tutorial-tours.ts +10 -0
  186. package/app/pages/index.vue +42 -8
  187. package/app/stores/board/context.ts +4 -3
  188. package/app/stores/board/placement.ts +8 -20
  189. package/app/stores/board/removal.ts +4 -13
  190. package/app/stores/board.spec.ts +10 -1
  191. package/app/stores/board.ts +3 -2
  192. package/app/stores/workView.ts +51 -0
  193. package/app/stores/workspaceQueue.ts +148 -0
  194. package/app/utils/bootstrapSteps.ts +11 -2
  195. package/app/utils/pipelineRender.ts +21 -8
  196. package/app/utils/queueSections.spec.ts +111 -0
  197. package/app/utils/queueSections.ts +117 -0
  198. package/app/utils/workView.spec.ts +16 -0
  199. package/app/utils/workView.ts +25 -0
  200. package/i18n/locales/de.json +73 -32
  201. package/i18n/locales/en.json +73 -32
  202. package/i18n/locales/es.json +73 -32
  203. package/i18n/locales/fr.json +73 -32
  204. package/i18n/locales/he.json +73 -32
  205. package/i18n/locales/it.json +73 -32
  206. package/i18n/locales/ja.json +73 -32
  207. package/i18n/locales/pl.json +73 -32
  208. package/i18n/locales/tr.json +73 -32
  209. package/i18n/locales/uk.json +73 -32
  210. package/package.json +1 -1
@@ -18,10 +18,11 @@ import type {
18
18
  } from '~/types/sandbox'
19
19
  import SectionLabel from '~/components/common/SectionLabel.vue'
20
20
  import IconButton from '~/components/common/IconButton.vue'
21
+ import EmptyState from '~/components/common/EmptyState.vue'
21
22
 
22
23
  const ui = useUiStore()
23
24
  const store = useSandboxStore()
24
- const toast = useToast()
25
+ const actionToast = useActionToast()
25
26
  const { present } = usePipelineErrorToast()
26
27
  const { t } = useI18n()
27
28
 
@@ -82,13 +83,9 @@ async function promote(version: SandboxPromptVersion) {
82
83
  promoting.value = version.id
83
84
  try {
84
85
  await store.promotePrompt(version)
85
- toast.add({
86
- title: t('sandbox.prompts.promoted', { agent: version.agentKind }),
87
- color: 'success',
88
- icon: 'i-lucide-rocket',
89
- })
90
- } catch {
91
- toast.add({ title: t('sandbox.prompts.promoteFailed'), color: 'error' })
86
+ actionToast.success('sandbox.prompts.promoted', { params: { agent: version.agentKind } })
87
+ } catch (error) {
88
+ present(error, 'sandbox.prompts.promoteFailed')
92
89
  } finally {
93
90
  promoting.value = null
94
91
  }
@@ -193,9 +190,9 @@ async function createAndRun() {
193
190
  },
194
191
  })
195
192
  name.value = ''
196
- toast.add({ title: t('sandbox.toast.running'), icon: 'i-lucide-flask-conical', color: 'info' })
193
+ actionToast.info('sandbox.toast.running')
197
194
  await store.launch(created.id)
198
- toast.add({ title: t('sandbox.toast.complete'), icon: 'i-lucide-check', color: 'success' })
195
+ actionToast.success('sandbox.toast.complete')
199
196
  } catch (e) {
200
197
  present(e, 'sandbox.toast.runFailed')
201
198
  }
@@ -255,7 +252,7 @@ async function saveVersion() {
255
252
  savingPrompt.value = true
256
253
  try {
257
254
  await store.saveVersion(editing.value.id, editText.value)
258
- toast.add({ title: t('sandbox.toast.versionSaved'), icon: 'i-lucide-check', color: 'success' })
255
+ actionToast.success('sandbox.toast.versionSaved')
259
256
  editing.value = null
260
257
  } catch (e) {
261
258
  present(e, 'sandbox.toast.saveFailed')
@@ -282,8 +279,12 @@ async function archive(prompt: SandboxPromptVersion) {
282
279
  :ui="{ content: 'max-w-5xl' }"
283
280
  >
284
281
  <template #body>
285
- <div v-if="store.loading" class="flex items-center justify-center py-12">
286
- <UIcon name="i-lucide-loader-circle" class="h-6 w-6 animate-spin text-muted" />
282
+ <div v-if="store.loading" class="space-y-4">
283
+ <USkeleton class="h-8 w-full" />
284
+ <div class="grid gap-4 lg:grid-cols-2">
285
+ <USkeleton class="h-80 w-full rounded-lg" />
286
+ <USkeleton class="h-80 w-full rounded-lg" />
287
+ </div>
287
288
  </div>
288
289
 
289
290
  <div
@@ -405,9 +406,12 @@ async function archive(prompt: SandboxPromptVersion) {
405
406
  />
406
407
  <span class="truncate">{{ m.label }}</span>
407
408
  </label>
408
- <p v-if="!store.selectableModels.length" class="text-xs text-dimmed">
409
- {{ t('sandbox.builder.noModels') }}
410
- </p>
409
+ <EmptyState
410
+ v-if="!store.selectableModels.length"
411
+ compact
412
+ icon="i-lucide-cpu"
413
+ :title="t('sandbox.builder.noModels')"
414
+ />
411
415
  </div>
412
416
  </div>
413
417
 
@@ -429,9 +433,12 @@ async function archive(prompt: SandboxPromptVersion) {
429
433
  />
430
434
  <span class="truncate">{{ f.name }}</span>
431
435
  </label>
432
- <p v-if="!kindFixtures.length" class="text-xs text-dimmed">
433
- {{ t('sandbox.builder.noFixtures') }}
434
- </p>
436
+ <EmptyState
437
+ v-if="!kindFixtures.length"
438
+ compact
439
+ icon="i-lucide-clipboard-list"
440
+ :title="t('sandbox.builder.noFixtures')"
441
+ />
435
442
  </div>
436
443
  </div>
437
444
 
@@ -577,9 +584,12 @@ async function archive(prompt: SandboxPromptVersion) {
577
584
  <span class="truncate text-toned">{{ x.name }}</span>
578
585
  <UBadge variant="soft" size="xs">{{ EXPERIMENT_STATUS_LABEL[x.status] }}</UBadge>
579
586
  </UButton>
580
- <p v-if="!store.experiments.length" class="text-xs text-dimmed">
581
- {{ t('sandbox.results.empty') }}
582
- </p>
587
+ <EmptyState
588
+ v-if="!store.experiments.length"
589
+ compact
590
+ icon="i-lucide-flask-conical"
591
+ :title="t('sandbox.results.empty')"
592
+ />
583
593
  </div>
584
594
  </div>
585
595
  </div>
@@ -718,9 +728,11 @@ async function archive(prompt: SandboxPromptVersion) {
718
728
  }}
719
729
  </p>
720
730
  </div>
721
- <p v-if="!store.fixtures.length" class="text-xs text-dimmed">
722
- {{ t('sandbox.fixtures.empty') }}
723
- </p>
731
+ <EmptyState
732
+ v-if="!store.fixtures.length"
733
+ icon="i-lucide-clipboard-list"
734
+ :title="t('sandbox.fixtures.empty')"
735
+ />
724
736
  </div>
725
737
  </div>
726
738
  </template>
@@ -11,6 +11,7 @@ import AccountFragmentSettings from '~/components/layout/AccountFragmentSettings
11
11
  import AccountSkillSettings from '~/components/layout/AccountSkillSettings.vue'
12
12
  import AccountFoundationalSettings from '~/components/layout/AccountFoundationalSettings.vue'
13
13
  import AccountRiskPolicySettings from '~/components/layout/AccountRiskPolicySettings.vue'
14
+ import EmptyState from '~/components/common/EmptyState.vue'
14
15
 
15
16
  const { t } = useI18n()
16
17
  const ui = useUiStore()
@@ -60,9 +61,11 @@ const tabs = computed(() => [
60
61
  <template>
61
62
  <UModal v-model:open="open" :title="t('settings.account.title')" :ui="{ content: 'max-w-3xl' }">
62
63
  <template #body>
63
- <p v-if="!accounts.activeAccountId" class="text-sm text-muted">
64
- {{ t('settings.account.noAccount') }}
65
- </p>
64
+ <EmptyState
65
+ v-if="!accounts.activeAccountId"
66
+ icon="i-lucide-building-2"
67
+ :title="t('settings.account.noAccount')"
68
+ />
66
69
  <UTabs
67
70
  v-else
68
71
  v-model="activeTab"
@@ -74,7 +74,7 @@ function boundLabel(key: PublicApiKey): string | null {
74
74
  ? t('settings.apiTokens.list.boundToYou')
75
75
  : t('settings.apiTokens.list.boundToOther', { user: key.actsAsUserId })
76
76
  }
77
- const toast = useToast()
77
+ const actionToast = useActionToast()
78
78
  const { present } = usePipelineErrorToast()
79
79
  const { confirmAction, toastDone } = useConfirmAction()
80
80
 
@@ -152,11 +152,7 @@ async function createToken() {
152
152
  label.value = ''
153
153
  scope.value = 'write'
154
154
  identity.value = 'system'
155
- toast.add({
156
- title: t('settings.apiTokens.toast.created'),
157
- icon: 'i-lucide-check',
158
- color: 'success',
159
- })
155
+ actionToast.success('settings.apiTokens.toast.created')
160
156
  } catch (e) {
161
157
  present(e, 'settings.apiTokens.toast.createFailed')
162
158
  } finally {
@@ -8,7 +8,7 @@ import { computed, reactive, ref, watch, type Ref } from 'vue'
8
8
  import SectionLabel from '~/components/common/SectionLabel.vue'
9
9
 
10
10
  const { t, n } = useI18n()
11
- const toast = useToast()
11
+ const actionToast = useActionToast()
12
12
  const { present } = usePipelineErrorToast()
13
13
 
14
14
  const settingsStore = useWorkspaceSettingsStore()
@@ -32,7 +32,7 @@ async function runSave(saving: Ref<boolean>, save: () => Promise<unknown>) {
32
32
  saving.value = true
33
33
  try {
34
34
  await save()
35
- toast.add({ title: t('settings.workspaceSettings.toast.budgetSaved'), color: 'success' })
35
+ actionToast.success('settings.workspaceSettings.toast.budgetSaved')
36
36
  try {
37
37
  await useWorkspaceStore().refresh()
38
38
  } catch {
@@ -20,7 +20,7 @@ import ToolServerChecklist from '~/components/settings/ToolServerChecklist.vue'
20
20
 
21
21
  const { t, d } = useI18n()
22
22
  const store = useCapabilityCredentialsStore()
23
- const toast = useToast()
23
+ const actionToast = useActionToast()
24
24
  const { confirmAction, toastDone } = useConfirmAction()
25
25
 
26
26
  // Which declaring capability wants a key. An exhaustive Record over the wire union, so a new
@@ -85,11 +85,7 @@ async function saveKey(key: string) {
85
85
  try {
86
86
  await store.save(key, value)
87
87
  drafts[key] = ''
88
- toast.add({
89
- title: t('settings.capabilityCredentials.toast.saved', { key }),
90
- icon: 'i-lucide-check',
91
- color: 'success',
92
- })
88
+ actionToast.success('settings.capabilityCredentials.toast.saved', { params: { key } })
93
89
  } catch (e) {
94
90
  present(e, 'settings.capabilityCredentials.toast.saveFailed')
95
91
  } finally {
@@ -18,7 +18,7 @@ type CloudflareConfig = CloudflareHandlerConfig['cloudflare']
18
18
 
19
19
  const { t } = useI18n()
20
20
  const infra = useInfraConfigStore()
21
- const toast = useToast()
21
+ const actionToast = useActionToast()
22
22
  const { present } = usePipelineErrorToast()
23
23
  const { confirmAction } = useConfirmAction()
24
24
 
@@ -101,11 +101,7 @@ async function save() {
101
101
  })
102
102
  token.value = ''
103
103
  editing.value = false
104
- toast.add({
105
- title: t('settings.infrastructure.handler.saved'),
106
- icon: 'i-lucide-check',
107
- color: 'success',
108
- })
104
+ actionToast.success('settings.infrastructure.handler.saved')
109
105
  } catch (e) {
110
106
  present(e, 'settings.infrastructure.handler.saveFailed')
111
107
  } finally {
@@ -133,7 +129,7 @@ async function remove() {
133
129
  busy.value = true
134
130
  try {
135
131
  await infra.unregisterHandler('cloudflare')
136
- toast.add({ title: t('settings.infrastructure.handler.removed'), icon: 'i-lucide-check' })
132
+ actionToast.success('settings.infrastructure.handler.removed')
137
133
  } catch (e) {
138
134
  present(e, 'settings.infrastructure.handler.saveFailed')
139
135
  } finally {
@@ -15,6 +15,7 @@ import { isSelectable } from '~/stores/models'
15
15
  import { uid } from '~/utils/catalog'
16
16
  import SectionLabel from '~/components/common/SectionLabel.vue'
17
17
  import IconButton from '~/components/common/IconButton.vue'
18
+ import EmptyState from '~/components/common/EmptyState.vue'
18
19
 
19
20
  const { t } = useI18n()
20
21
  const groups = useConsensusGroupsStore()
@@ -291,9 +292,12 @@ async function remove(group: ConsensusGroup) {
291
292
  </div>
292
293
  </div>
293
294
  </div>
294
- <p v-else class="py-4 text-center text-sm text-dimmed">
295
- {{ t('settings.consensusGroups.list.empty') }}
296
- </p>
295
+ <EmptyState
296
+ v-else
297
+ compact
298
+ icon="i-lucide-users"
299
+ :title="t('settings.consensusGroups.list.empty')"
300
+ />
297
301
  </template>
298
302
 
299
303
  <!-- ===== editor ===== -->
@@ -7,6 +7,7 @@
7
7
  import { computed, reactive, ref } from 'vue'
8
8
  import type { CustomManifestType } from '@cat-factory/contracts'
9
9
  import SectionLabel from '~/components/common/SectionLabel.vue'
10
+ import EmptyState from '~/components/common/EmptyState.vue'
10
11
 
11
12
  const { t } = useI18n()
12
13
  const infra = useInfraConfigStore()
@@ -140,9 +141,12 @@ async function remove(type: CustomManifestType) {
140
141
  </div>
141
142
  </li>
142
143
  </ul>
143
- <p v-else class="text-2xs text-dimmed">
144
- {{ t('settings.infrastructure.customType.empty') }}
145
- </p>
144
+ <EmptyState
145
+ v-else
146
+ compact
147
+ icon="i-lucide-boxes"
148
+ :title="t('settings.infrastructure.customType.empty')"
149
+ />
146
150
 
147
151
  <!-- Add / edit a workspace-defined type. -->
148
152
  <div class="space-y-2 border-t border-default pt-3">
@@ -24,7 +24,7 @@ import {
24
24
  const { t } = useI18n()
25
25
  const infra = useInfraConfigStore()
26
26
  const settingsStore = useWorkspaceSettingsStore()
27
- const toast = useToast()
27
+ const actionToast = useActionToast()
28
28
  const { present } = usePipelineErrorToast()
29
29
 
30
30
  onMounted(() => {
@@ -90,7 +90,7 @@ async function save() {
90
90
  defaultProvisionType: selection.value.type,
91
91
  defaultProvisionManifestId: selection.value.manifestId,
92
92
  })
93
- toast.add({ title: t('settings.defaultProvision.saved'), color: 'success' })
93
+ actionToast.success('settings.defaultProvision.saved')
94
94
  } catch (e) {
95
95
  present(e, 'settings.defaultProvision.saveFailed')
96
96
  } finally {
@@ -36,7 +36,7 @@ const { t } = useI18n()
36
36
  const infra = useInfraConfigStore()
37
37
  const auth = useAuthStore()
38
38
  const ui = useUiStore()
39
- const toast = useToast()
39
+ const actionToast = useActionToast()
40
40
  const { present } = usePipelineErrorToast()
41
41
  const { confirmAction } = useConfirmAction()
42
42
 
@@ -403,20 +403,16 @@ async function removeCustom() {
403
403
  }
404
404
 
405
405
  function toastSaved() {
406
- toast.add({
407
- title: t('settings.infrastructure.handler.saved'),
408
- icon: 'i-lucide-check',
409
- color: 'success',
410
- })
406
+ actionToast.success('settings.infrastructure.handler.saved')
411
407
  }
412
408
  function toastRemoved() {
413
- toast.add({ title: t('settings.infrastructure.handler.removed'), icon: 'i-lucide-check' })
409
+ actionToast.success('settings.infrastructure.handler.removed')
414
410
  }
415
411
  </script>
416
412
 
417
413
  <template>
418
414
  <!-- Only render the configurator once the handler bundle has actually resolved (available
419
- === true). While it's still being probed (null) show a loading line instead of flashing
415
+ === true). While it's still being probed (null) show skeleton sections instead of flashing
420
416
  the full form, and render nothing when the integration is off (false). -->
421
417
  <div v-if="infra.available === true" class="space-y-5">
422
418
  <p class="text-xs text-muted">{{ t('settings.infrastructure.handler.intro') }}</p>
@@ -629,7 +625,8 @@ function toastRemoved() {
629
625
  </div>
630
626
  </section>
631
627
  </div>
632
- <p v-else-if="infra.available === null" class="text-xs text-dimmed">
633
- {{ t('settings.infrastructure.handler.loading') }}
634
- </p>
628
+ <div v-else-if="infra.available === null" class="space-y-5">
629
+ <USkeleton class="h-4 w-2/3" />
630
+ <USkeleton v-for="n in 3" :key="n" class="h-24 w-full rounded-lg" />
631
+ </div>
635
632
  </template>
@@ -17,12 +17,13 @@
17
17
  import { computed, onMounted, ref, watch } from 'vue'
18
18
  import type { TaskSourceKind, TaskSourceState, TrackerKind } from '~/types/domain'
19
19
  import TaskSourceCard from '~/components/settings/TaskSourceCard.vue'
20
+ import EmptyState from '~/components/common/EmptyState.vue'
20
21
 
21
22
  const { t } = useI18n()
22
23
  const tracker = useTrackerStore()
23
24
  const tasks = useTasksStore()
24
25
  const ui = useUiStore()
25
- const toast = useToast()
26
+ const actionToast = useActionToast()
26
27
  const { present } = usePipelineErrorToast()
27
28
 
28
29
  // --- filing tracker + writeback (one Save, persisted on tracker settings) -----
@@ -114,11 +115,7 @@ async function save() {
114
115
  writebackResolveOnMerge: resolveOnMerge.value,
115
116
  writebackQuestionsOnPark: questionsOnPark.value,
116
117
  })
117
- toast.add({
118
- title: t('settings.issueTracker.toast.saved'),
119
- icon: 'i-lucide-check',
120
- color: 'success',
121
- })
118
+ actionToast.success('settings.issueTracker.toast.saved')
122
119
  } catch (e) {
123
120
  present(e, 'settings.issueTracker.toast.saveFailed')
124
121
  } finally {
@@ -379,9 +376,12 @@ async function checkSetup(source: TaskSourceKind) {
379
376
  @remedy="openRemedy(source)"
380
377
  />
381
378
 
382
- <p v-if="tasks.sources.length === 0" class="text-2xs text-dimmed">
383
- {{ t('settings.issueTracker.linking.none') }}
384
- </p>
379
+ <EmptyState
380
+ v-if="tasks.sources.length === 0"
381
+ compact
382
+ icon="i-lucide-list-todo"
383
+ :title="t('settings.issueTracker.linking.none')"
384
+ />
385
385
  </section>
386
386
 
387
387
  <!-- 3. Writeback ---------------------------------------------------------->
@@ -13,7 +13,7 @@ import { reactive, ref, watch } from 'vue'
13
13
 
14
14
  const { t } = useI18n()
15
15
  const store = useLocalSettingsStore()
16
- const toast = useToast()
16
+ const actionToast = useActionToast()
17
17
  const { present } = usePipelineErrorToast()
18
18
 
19
19
  const saving = ref(false)
@@ -60,11 +60,7 @@ async function save() {
60
60
  },
61
61
  checkout: { workspaceRoot: draft.workspaceRoot.trim() || '/workspace', cleanKeep },
62
62
  })
63
- toast.add({
64
- title: t('settings.localMode.toast.saved'),
65
- icon: 'i-lucide-check',
66
- color: 'success',
67
- })
63
+ actionToast.success('settings.localMode.toast.saved')
68
64
  } catch (e) {
69
65
  present(e, 'settings.localMode.toast.saveFailed')
70
66
  } finally {
@@ -23,7 +23,7 @@ import IconButton from '~/components/common/IconButton.vue'
23
23
  const { t } = useI18n()
24
24
  const ui = useUiStore()
25
25
  const store = useLocalModelsStore()
26
- const toast = useToast()
26
+ const actionToast = useActionToast()
27
27
  const { present } = usePipelineErrorToast()
28
28
  const { confirm } = useConfirm()
29
29
 
@@ -230,12 +230,10 @@ async function save() {
230
230
  models: selected.value.map((id) => ({ id, ...modalityOf(imageInput.value[id]) })),
231
231
  })
232
232
  apiKey.value = ''
233
- toast.add({
234
- title: t('settings.localModelEndpoints.toast.saved', {
233
+ actionToast.success('settings.localModelEndpoints.toast.saved', {
234
+ params: {
235
235
  name: LOCAL_RUNNER_LABELS[provider.value],
236
- }),
237
- icon: 'i-lucide-check',
238
- color: 'success',
236
+ },
239
237
  })
240
238
  } catch (e) {
241
239
  present(e, 'settings.localModelEndpoints.toast.saveFailed')
@@ -261,7 +259,7 @@ async function remove(p: LocalRunner) {
261
259
  // The row is gone from the store, so re-seeding the draft it was showing yields the
262
260
  // fresh-runner defaults: the same reset, without a second copy of what a reset means.
263
261
  if (provider.value === p) seedDraft(p)
264
- toast.add({ title: t('settings.localModelEndpoints.toast.removed'), icon: 'i-lucide-check' })
262
+ actionToast.success('settings.localModelEndpoints.toast.removed')
265
263
  } catch (e) {
266
264
  present(e, 'settings.localModelEndpoints.toast.removeFailed')
267
265
  } finally {
@@ -145,9 +145,14 @@ function backToApp() {
145
145
  data-testid="mcp-authorize"
146
146
  >
147
147
  <div class="w-full max-w-md rounded-xl border border-default bg-default/80 p-8 backdrop-blur">
148
- <template v-if="screen === 'loading'">
149
- <UIcon name="i-lucide-loader" class="mx-auto h-10 w-10 animate-spin text-primary" />
150
- </template>
148
+ <div v-if="screen === 'loading'" class="flex flex-col items-center">
149
+ <USkeleton class="mb-3 h-10 w-10 rounded-full" />
150
+ <USkeleton class="mb-2 h-6 w-3/4" />
151
+ <USkeleton class="mb-6 h-4 w-full" />
152
+ <USkeleton class="mb-4 h-8 w-full" />
153
+ <USkeleton class="mb-6 h-16 w-full" />
154
+ <USkeleton class="h-8 w-full" />
155
+ </div>
151
156
 
152
157
  <template v-else-if="screen === 'failed'">
153
158
  <UIcon name="i-lucide-alert-triangle" class="mx-auto mb-3 h-10 w-10 text-app-error-400" />
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { onMounted, ref } from 'vue'
3
+ import Spinner from '~/components/common/Spinner.vue'
3
4
 
4
5
  // Where a vendor's authorization server sends the operator's browser back to after they approve a
5
6
  // remote MCP tool server (`/mcp-oauth-callback?code=…&state=…`).
@@ -69,7 +70,7 @@ function backToApp() {
69
70
  class="w-full max-w-sm rounded-xl border border-default bg-default/80 p-8 text-center backdrop-blur"
70
71
  >
71
72
  <template v-if="state === 'working'">
72
- <UIcon name="i-lucide-loader" class="mx-auto mb-3 h-10 w-10 animate-spin text-primary" />
73
+ <Spinner class="mx-auto mb-3 h-10 w-10 text-primary" />
73
74
  <h1 class="mb-1 text-lg font-semibold text-highlighted">
74
75
  {{ t('settings.toolServers.oauth.callback.working') }}
75
76
  </h1>
@@ -24,6 +24,7 @@ import ProviderPreferenceEditor from '~/components/settings/ProviderPreferenceEd
24
24
  import { showOverrideField } from '~/utils/uiMode'
25
25
  import SectionLabel from '~/components/common/SectionLabel.vue'
26
26
  import IconButton from '~/components/common/IconButton.vue'
27
+ import EmptyState from '~/components/common/EmptyState.vue'
27
28
  import { FULL_BLEED_TAKEOVER_CLASS } from '~/utils/safeArea'
28
29
 
29
30
  const { t } = useI18n()
@@ -36,7 +37,7 @@ const agentTier = useAgentTierStore()
36
37
  const creds = useVendorCredentialsStore()
37
38
  const workspace = useWorkspaceStore()
38
39
  const { present } = usePipelineErrorToast()
39
- const toast = useToast()
40
+ const actionToast = useActionToast()
40
41
  const { confirm } = useConfirm()
41
42
 
42
43
  const open = computed({
@@ -93,13 +94,25 @@ const hiddenByTier = computed(() =>
93
94
  filter.value.trim() ? 0 : configurableKinds.value.length - tieredKinds.value.length,
94
95
  )
95
96
 
97
+ // Set when the catalog read rejects, so the preset list states the failure instead of holding a
98
+ // skeleton that never resolves (`models.loaded` stays false after a failed read).
99
+ const catalogFailed = ref(false)
100
+
101
+ function loadCatalog() {
102
+ catalogFailed.value = false
103
+ models.ensureLoaded(workspace.workspaceId ?? undefined).catch((error) => {
104
+ catalogFailed.value = true
105
+ present(error, 'settings.modelConfiguration.toast.catalogFailed')
106
+ })
107
+ }
108
+
96
109
  watch(
97
110
  open,
98
111
  (isOpen) => {
99
112
  if (isOpen) {
100
113
  editor.value = null
101
114
  filter.value = ''
102
- void models.ensureLoaded(workspace.workspaceId ?? undefined)
115
+ loadCatalog()
103
116
  if (workspace.workspaceId) void creds.load(workspace.workspaceId)
104
117
  }
105
118
  },
@@ -258,11 +271,8 @@ async function save() {
258
271
  // NOT through `present`: that funnel classifies a BACKEND failure, and a synthesized local
259
272
  // `Error` carries no envelope and no status, so it lands on the network-fault description and
260
273
  // tells the user the server could not be reached about a check that never left the browser.
261
- toast.add({
262
- title: t('settings.modelConfiguration.toast.nameRequiredTitle'),
274
+ actionToast.error('settings.modelConfiguration.toast.nameRequiredTitle', {
263
275
  description: t('settings.modelConfiguration.toast.nameRequiredBody'),
264
- color: 'warning',
265
- icon: 'i-lucide-triangle-alert',
266
276
  })
267
277
  return
268
278
  }
@@ -372,9 +382,25 @@ async function save() {
372
382
  </UButton>
373
383
  </div>
374
384
 
375
- <p v-if="models.models.length === 0" class="py-4 text-center text-sm text-dimmed">
376
- {{ t('settings.modelConfiguration.list.loadingCatalog') }}
377
- </p>
385
+ <UAlert
386
+ v-if="catalogFailed"
387
+ color="error"
388
+ variant="subtle"
389
+ icon="i-lucide-triangle-alert"
390
+ :title="t('settings.modelConfiguration.list.catalogFailed')"
391
+ :actions="[
392
+ {
393
+ label: t('common.retry'),
394
+ color: 'neutral',
395
+ variant: 'outline',
396
+ onClick: loadCatalog,
397
+ },
398
+ ]"
399
+ />
400
+
401
+ <div v-else-if="!models.loaded" class="space-y-3">
402
+ <USkeleton v-for="n in 3" :key="n" class="h-20 w-full rounded-lg" />
403
+ </div>
378
404
 
379
405
  <div v-else class="space-y-3">
380
406
  <div
@@ -443,9 +469,11 @@ async function save() {
443
469
  </span>
444
470
  </div>
445
471
  </div>
446
- <p v-if="sortedPresets.length === 0" class="py-6 text-center text-sm text-dimmed">
447
- {{ t('settings.modelConfiguration.list.empty') }}
448
- </p>
472
+ <EmptyState
473
+ v-if="sortedPresets.length === 0"
474
+ icon="i-lucide-sliders-horizontal"
475
+ :title="t('settings.modelConfiguration.list.empty')"
476
+ />
449
477
  </div>
450
478
 
451
479
  <!-- The consensus-GROUP library: which model PANELS review the workspace's heavier
@@ -549,12 +577,12 @@ async function save() {
549
577
  </UButton>
550
578
  </UDropdownMenu>
551
579
  </div>
552
- <p
580
+ <EmptyState
553
581
  v-if="filteredKinds.length === 0"
554
- class="px-4 py-6 text-center text-sm text-dimmed"
555
- >
556
- {{ t('settings.modelConfiguration.editor.noAgentsMatch', { filter }) }}
557
- </p>
582
+ compact
583
+ icon="i-lucide-search-x"
584
+ :title="t('settings.modelConfiguration.editor.noAgentsMatch', { filter })"
585
+ />
558
586
  </div>
559
587
  </div>
560
588
 
@@ -12,7 +12,7 @@ import SecretInput from '~/components/common/SecretInput.vue'
12
12
  const { t } = useI18n()
13
13
  const ui = useUiStore()
14
14
  const store = useReleaseHealthStore()
15
- const toast = useToast()
15
+ const actionToast = useActionToast()
16
16
  const { present } = usePipelineErrorToast()
17
17
  const { confirmAction, toastDone } = useConfirmAction()
18
18
 
@@ -66,21 +66,14 @@ async function saveIncident() {
66
66
  }
67
67
  if (incidentIo.apiKey.trim()) input.incidentIo = { apiKey: incidentIo.apiKey.trim() }
68
68
  if (!input.pagerDuty && !input.incidentIo) {
69
- toast.add({
70
- title: t('settings.observabilityConnection.toast.incidentCredsRequired'),
71
- color: 'error',
72
- })
69
+ actionToast.error('settings.observabilityConnection.toast.incidentCredsRequired')
73
70
  return
74
71
  }
75
72
  await store.saveIncident(input)
76
73
  pagerDuty.apiToken = ''
77
74
  pagerDuty.fromEmail = ''
78
75
  incidentIo.apiKey = ''
79
- toast.add({
80
- title: t('settings.observabilityConnection.toast.incidentSaved'),
81
- icon: 'i-lucide-check',
82
- color: 'success',
83
- })
76
+ actionToast.success('settings.observabilityConnection.toast.incidentSaved')
84
77
  } catch (e) {
85
78
  present(e, 'settings.observabilityConnection.toast.incidentSaveFailed')
86
79
  } finally {
@@ -111,11 +104,7 @@ async function saveConnection() {
111
104
  })
112
105
  datadog.apiKey = ''
113
106
  datadog.appKey = ''
114
- toast.add({
115
- title: t('settings.observabilityConnection.toast.connected'),
116
- icon: 'i-lucide-check',
117
- color: 'success',
118
- })
107
+ actionToast.success('settings.observabilityConnection.toast.connected')
119
108
  } catch (e) {
120
109
  present(e, 'settings.observabilityConnection.toast.connectFailed')
121
110
  } finally {