@cat-factory/app 0.311.2 → 0.311.3

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 (192) hide show
  1. package/README.md +65 -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/bootstrap/AdoptionReviewModal.vue +2 -5
  17. package/app/components/bootstrap/BootstrapModal.vue +6 -15
  18. package/app/components/bootstrap/BootstrapRunSteps.vue +3 -1
  19. package/app/components/brainstorm/BrainstormWindow.vue +32 -26
  20. package/app/components/bugFishing/BugFishingWindow.vue +15 -13
  21. package/app/components/clarity/ClarityReviewWindow.vue +31 -26
  22. package/app/components/common/ClarificationItem.vue +3 -1
  23. package/app/components/common/EmptyState.vue +41 -19
  24. package/app/components/common/InterviewGateNotice.vue +3 -1
  25. package/app/components/common/Spinner.vue +28 -0
  26. package/app/components/consensus/ConsensusSessionWindow.vue +5 -5
  27. package/app/components/docs/DocInterviewWindow.vue +6 -6
  28. package/app/components/documents/ContextDocumentPicker.vue +2 -2
  29. package/app/components/documents/DocumentImportModal.vue +9 -8
  30. package/app/components/documents/DocumentSourceConnectModal.vue +10 -10
  31. package/app/components/documents/DocumentTemplatesModal.vue +13 -7
  32. package/app/components/documents/RepoContextDocPicker.vue +2 -2
  33. package/app/components/documents/SpawnPreviewModal.vue +4 -6
  34. package/app/components/documents/StartFromDesignModal.vue +2 -1
  35. package/app/components/documents/TaskContextDocs.vue +4 -5
  36. package/app/components/environments/EnvironmentStatusPanel.vue +11 -8
  37. package/app/components/environments/steps/EnvPickStep.vue +7 -3
  38. package/app/components/environments/steps/EnvPreflightStep.vue +6 -5
  39. package/app/components/focus/BlockFocusView.vue +6 -5
  40. package/app/components/followUp/FollowUpWindow.vue +7 -9
  41. package/app/components/forkDecision/ForkDecisionWindow.vue +9 -8
  42. package/app/components/foundational/FoundationalServiceCatalogList.vue +9 -5
  43. package/app/components/foundational/FoundationalServiceRegistry.vue +11 -7
  44. package/app/components/foundational/FoundationalServiceSources.vue +20 -22
  45. package/app/components/foundational/FoundationalSuppressions.vue +2 -2
  46. package/app/components/foundational/ServiceCatalogConnection.vue +15 -15
  47. package/app/components/fragments/FragmentLibraryManager.vue +36 -34
  48. package/app/components/fragments/FragmentSelector.vue +7 -3
  49. package/app/components/gates/GateResultView.vue +6 -9
  50. package/app/components/github/AddServiceFromRepoModal.vue +6 -10
  51. package/app/components/github/GitHubConnect.vue +10 -17
  52. package/app/components/github/GitHubPanel.vue +44 -47
  53. package/app/components/github/RepoTreeBrowser.vue +9 -5
  54. package/app/components/guidedReview/GuidedReviewModal.vue +16 -6
  55. package/app/components/guidedReview/GuidedReviewOverview.vue +2 -1
  56. package/app/components/guidedReview/GuidedReviewPicker.vue +10 -7
  57. package/app/components/guidedReview/GuidedReviewThread.vue +9 -4
  58. package/app/components/humanTest/HumanTestWindow.vue +2 -1
  59. package/app/components/initiative/InitiativePlanningWindow.vue +6 -6
  60. package/app/components/initiative/InitiativeTrackerWindow.vue +11 -11
  61. package/app/components/judge/JudgeResultView.vue +2 -7
  62. package/app/components/kaizen/KaizenPanel.vue +20 -5
  63. package/app/components/kaizen/KaizenStepStatus.vue +2 -1
  64. package/app/components/layout/AccountAuditLog.vue +11 -3
  65. package/app/components/layout/AccountDeploymentSettings.vue +17 -62
  66. package/app/components/layout/AccountFailureKindRules.vue +6 -5
  67. package/app/components/layout/AccountModelPolicySettings.vue +2 -6
  68. package/app/components/layout/AccountPlatformAlertSettings.vue +5 -13
  69. package/app/components/layout/AccountRiskPolicySettings.vue +13 -14
  70. package/app/components/layout/AccountRunCredentialSettings.vue +2 -2
  71. package/app/components/layout/AccountTeamSettings.vue +12 -9
  72. package/app/components/layout/BoardSwitcher.vue +2 -2
  73. package/app/components/layout/BoardToolbar.vue +3 -11
  74. package/app/components/layout/CommandBar.vue +7 -3
  75. package/app/components/layout/ConnectionStatusBanner.vue +2 -1
  76. package/app/components/layout/IntegrationsHub.vue +7 -3
  77. package/app/components/layout/ModelProvidersHub.vue +7 -3
  78. package/app/components/layout/NotificationsInbox.vue +3 -11
  79. package/app/components/layout/WorkspaceMembersSettings.vue +12 -5
  80. package/app/components/media/ArtifactLightbox.vue +4 -3
  81. package/app/components/notifications/NotificationSettingsPanel.vue +4 -11
  82. package/app/components/observability/ToolCallList.vue +12 -17
  83. package/app/components/panels/AgentStepDetail.vue +5 -5
  84. package/app/components/panels/GenericStructuredResultView.vue +6 -9
  85. package/app/components/panels/InspectorPanel.vue +2 -1
  86. package/app/components/panels/MergerResultView.vue +5 -6
  87. package/app/components/panels/ObservabilityPanel.vue +26 -41
  88. package/app/components/panels/OperatorDashboardPanel.vue +29 -17
  89. package/app/components/panels/ReportsPanel.vue +17 -11
  90. package/app/components/panels/ReportsSpendBreakdown.vue +7 -3
  91. package/app/components/panels/RunDetailLoadState.vue +4 -3
  92. package/app/components/panels/StepContainerStatus.vue +4 -3
  93. package/app/components/panels/StepDelegatedStatus.vue +3 -2
  94. package/app/components/panels/StepMetadataCard.vue +6 -8
  95. package/app/components/panels/StepRunMeta.vue +2 -5
  96. package/app/components/panels/inspector/ContainerSummary.vue +7 -1
  97. package/app/components/panels/inspector/EpicChildren.vue +7 -3
  98. package/app/components/panels/inspector/FrontendConfig.vue +7 -3
  99. package/app/components/panels/inspector/InitiativeInspector.vue +5 -2
  100. package/app/components/panels/inspector/ServiceConnections.vue +7 -3
  101. package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +2 -6
  102. package/app/components/panels/inspector/ServiceTestSecrets.vue +2 -6
  103. package/app/components/panels/inspector/ServiceTestingContext.vue +2 -6
  104. package/app/components/panels/inspector/ServiceValidationConfig.vue +38 -39
  105. package/app/components/panels/inspector/TaskExecution.vue +5 -6
  106. package/app/components/panels/inspector/TaskRunSettings.vue +7 -3
  107. package/app/components/panels/inspector/TaskStructure.vue +0 -1
  108. package/app/components/pipeline/AgentPromptEditor.vue +25 -18
  109. package/app/components/pipeline/PipelineBuilder.vue +14 -24
  110. package/app/components/pipeline/PipelineProgress.vue +16 -17
  111. package/app/components/prReview/PrReviewPhaseBadge.vue +3 -2
  112. package/app/components/prReview/PrReviewWindow.vue +16 -20
  113. package/app/components/providers/ApiKeysSection.vue +2 -6
  114. package/app/components/providers/PersonalCredentialModal.vue +2 -6
  115. package/app/components/providers/PersonalSubscriptionSection.vue +3 -7
  116. package/app/components/providers/VendorCredentialsModal.vue +2 -6
  117. package/app/components/provisioning/ProvisioningLogsDrawer.vue +10 -6
  118. package/app/components/ralph/RalphLoopResultView.vue +2 -7
  119. package/app/components/requirements/RequirementsReviewWindow.vue +44 -39
  120. package/app/components/sandbox/SandboxPanel.vue +37 -25
  121. package/app/components/settings/AccountSettingsPanel.vue +6 -3
  122. package/app/components/settings/ApiTokensPanel.vue +2 -6
  123. package/app/components/settings/BudgetSettings.vue +2 -2
  124. package/app/components/settings/CapabilityCredentialsPanel.vue +2 -6
  125. package/app/components/settings/CloudflareHandlerSection.vue +3 -7
  126. package/app/components/settings/ConsensusGroupsSection.vue +7 -3
  127. package/app/components/settings/CustomManifestTypeEditor.vue +7 -3
  128. package/app/components/settings/DefaultProvisionTypeSection.vue +2 -2
  129. package/app/components/settings/InfraHandlersConfigurator.vue +8 -11
  130. package/app/components/settings/IssueTrackerPanel.vue +9 -9
  131. package/app/components/settings/LocalContainerPoolSettings.vue +2 -6
  132. package/app/components/settings/LocalModelEndpointsPanel.vue +5 -7
  133. package/app/components/settings/McpAuthorizeScreen.vue +8 -3
  134. package/app/components/settings/McpOAuthCallbackScreen.vue +2 -1
  135. package/app/components/settings/ModelConfigurationPanel.vue +45 -17
  136. package/app/components/settings/ObservabilityConnectionPanel.vue +4 -15
  137. package/app/components/settings/OpenRouterCatalogPanel.vue +8 -24
  138. package/app/components/settings/PackageRegistriesPanel.vue +2 -6
  139. package/app/components/settings/ProviderConnectionTab.vue +3 -7
  140. package/app/components/settings/RiskPolicyPanel.vue +4 -16
  141. package/app/components/settings/SharedStacksPanel.vue +8 -18
  142. package/app/components/settings/TaskTypeSuppressionsPanel.vue +10 -6
  143. package/app/components/settings/UsageSettings.vue +11 -2
  144. package/app/components/settings/UserSecretsSection.vue +5 -7
  145. package/app/components/settings/WorkspaceMetadataSettings.vue +9 -9
  146. package/app/components/settings/WorkspaceSettingsPanel.vue +3 -11
  147. package/app/components/skills/ReviewSkillQueue.vue +7 -3
  148. package/app/components/skills/SkillLibraryManager.vue +20 -18
  149. package/app/components/slack/SlackPanel.vue +5 -8
  150. package/app/components/spec/ServiceSpecWindow.vue +32 -29
  151. package/app/components/tasks/BugHuntModal.vue +23 -19
  152. package/app/components/tasks/ContextIssuePicker.vue +2 -2
  153. package/app/components/tasks/TaskContextIssues.vue +4 -5
  154. package/app/components/tasks/TaskImportModal.vue +10 -8
  155. package/app/components/tasks/TaskSourceConnectModal.vue +11 -11
  156. package/app/components/testing/TestReportWindow.vue +6 -12
  157. package/app/components/tutorial/TutorialCatalogue.vue +7 -3
  158. package/app/components/tutorial/TutorialOverlay.vue +2 -1
  159. package/app/components/tutorial/TutorialPrompt.vue +7 -3
  160. package/app/components/vcs/GitLabConnect.vue +2 -6
  161. package/app/components/visualConfirm/VisualConfirmationWindow.vue +9 -4
  162. package/app/composables/toastTitleKeys.spec.ts +143 -0
  163. package/app/composables/useActionToast.spec.ts +71 -0
  164. package/app/composables/useActionToast.ts +101 -0
  165. package/app/composables/useBlockDeletion.ts +3 -6
  166. package/app/composables/useConfirmAction.ts +5 -3
  167. package/app/composables/useContextLinking.spec.ts +3 -0
  168. package/app/composables/useContextLinking.ts +11 -16
  169. package/app/composables/useCopyToClipboard.ts +4 -3
  170. package/app/composables/useNavContributions.ts +4 -5
  171. package/app/composables/usePipelineErrorToast.spec.ts +24 -0
  172. package/app/composables/usePipelineErrorToast.ts +26 -2
  173. package/app/composables/usePipelineLibraryActions.ts +8 -9
  174. package/app/pages/index.vue +3 -2
  175. package/app/stores/board/context.ts +4 -3
  176. package/app/stores/board/placement.ts +8 -20
  177. package/app/stores/board/removal.ts +4 -13
  178. package/app/stores/board.spec.ts +10 -1
  179. package/app/stores/board.ts +3 -2
  180. package/app/utils/bootstrapSteps.ts +11 -2
  181. package/app/utils/pipelineRender.ts +21 -8
  182. package/i18n/locales/de.json +12 -30
  183. package/i18n/locales/en.json +12 -30
  184. package/i18n/locales/es.json +12 -30
  185. package/i18n/locales/fr.json +12 -30
  186. package/i18n/locales/he.json +12 -30
  187. package/i18n/locales/it.json +12 -30
  188. package/i18n/locales/ja.json +12 -30
  189. package/i18n/locales/pl.json +12 -30
  190. package/i18n/locales/tr.json +12 -30
  191. package/i18n/locales/uk.json +12 -30
  192. package/package.json +1 -1
@@ -29,11 +29,13 @@ import type {
29
29
  ReviewItemStatus,
30
30
  } from '~/types/requirements'
31
31
  import SectionLabel from '~/components/common/SectionLabel.vue'
32
+ import Spinner from '~/components/common/Spinner.vue'
33
+ import EmptyState from '~/components/common/EmptyState.vue'
32
34
 
33
35
  const board = useBoardStore()
34
36
  const requirements = useRequirementsStore()
35
37
  const models = useModelsStore()
36
- const toast = useToast()
38
+ const actionToast = useActionToast()
37
39
  const { present } = usePipelineErrorToast()
38
40
  const { t } = useI18n()
39
41
  const access = useWorkspaceAccess()
@@ -574,18 +576,18 @@ async function requestRecommendations() {
574
576
  // ran inline and the recommendations are already settled. Tell the human which actually
575
577
  // happened rather than always promising a background callback.
576
578
  const stillGenerating = (updated?.recommendations ?? []).some((r) => r.status === 'pending')
577
- toast.add(
578
- stillGenerating
579
- ? {
580
- title: t('requirements.toast.preparingRecommendations', { count: n }, n),
581
- description: t('requirements.toast.preparingRecommendationsDescription'),
582
- icon: 'i-lucide-sparkles',
583
- }
584
- : {
585
- title: t('requirements.toast.recommendationsReady', { count: n }, n),
586
- icon: 'i-lucide-sparkles',
587
- },
588
- )
579
+ if (stillGenerating) {
580
+ actionToast.info('requirements.toast.preparingRecommendations', {
581
+ params: { count: n },
582
+ plural: n,
583
+ description: t('requirements.toast.preparingRecommendationsDescription'),
584
+ })
585
+ } else {
586
+ actionToast.success('requirements.toast.recommendationsReady', {
587
+ params: { count: n },
588
+ plural: n,
589
+ })
590
+ }
589
591
  } catch (e) {
590
592
  present(e, 'requirements.errors.requestRecommendations')
591
593
  }
@@ -634,10 +636,8 @@ async function incorporate(feedback?: string) {
634
636
  showRedo.value = false
635
637
  // The fold + re-review now run in the durable driver. Hand the user back to the board;
636
638
  // a notification calls them back only if the re-review needs more input.
637
- toast.add({
638
- title: t('requirements.toast.incorporating'),
639
+ actionToast.info('requirements.toast.incorporating', {
639
640
  description: t('requirements.toast.incorporatingDescription'),
640
- icon: 'i-lucide-wand-sparkles',
641
641
  })
642
642
  close()
643
643
  }
@@ -647,15 +647,16 @@ async function reReview() {
647
647
  try {
648
648
  const updated = await requirements.reReview(blockId.value)
649
649
  const newFindings = requirements.openCount(updated)
650
- toast.add({
651
- title:
652
- updated.status === 'incorporated'
653
- ? t('requirements.toast.reviewerSatisfied')
654
- : updated.status === 'exceeded'
655
- ? t('requirements.toast.iterationLimitReached')
656
- : t('requirements.toast.newFindings', { count: newFindings }, newFindings),
657
- icon: 'i-lucide-sparkles',
658
- })
650
+ if (updated.status === 'incorporated') {
651
+ actionToast.success('requirements.toast.reviewerSatisfied')
652
+ } else if (updated.status === 'exceeded') {
653
+ actionToast.warning('requirements.toast.iterationLimitReached')
654
+ } else {
655
+ actionToast.info('requirements.toast.newFindings', {
656
+ params: { count: newFindings },
657
+ plural: newFindings,
658
+ })
659
+ }
659
660
  } catch (e) {
660
661
  present(e, 'requirements.errors.reReview')
661
662
  }
@@ -667,7 +668,7 @@ async function proceed() {
667
668
  try {
668
669
  await flushDrafts()
669
670
  await requirements.proceed(blockId.value)
670
- toast.add({ title: t('requirements.toast.proceeding'), icon: 'i-lucide-arrow-right' })
671
+ actionToast.info('requirements.toast.proceeding')
671
672
  } catch (e) {
672
673
  present(e, 'requirements.errors.proceed')
673
674
  } finally {
@@ -681,12 +682,12 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
681
682
  try {
682
683
  await requirements.resolveExceeded(blockId.value, choice)
683
684
  if (choice === 'stop-reset') {
684
- toast.add({ title: t('requirements.toast.taskReset'), icon: 'i-lucide-undo' })
685
+ actionToast.info('requirements.toast.taskReset')
685
686
  close()
686
687
  } else if (choice === 'proceed') {
687
- toast.add({ title: t('requirements.toast.proceeding'), icon: 'i-lucide-arrow-right' })
688
+ actionToast.info('requirements.toast.proceeding')
688
689
  } else {
689
- toast.add({ title: t('requirements.toast.extraRoundGranted'), icon: 'i-lucide-rotate-cw' })
690
+ actionToast.info('requirements.toast.extraRoundGranted')
690
691
  }
691
692
  } catch (e) {
692
693
  present(e, 'requirements.errors.resolveReview')
@@ -734,20 +735,24 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
734
735
 
735
736
  <!-- empty state — the reviewer runs automatically as the first pipeline
736
737
  gate step, so there's nothing to do here until then -->
737
- <div
738
+ <EmptyState
738
739
  v-if="!review && !busy && !loading"
739
- class="rounded-lg border border-dashed border-muted p-8 text-center text-sm text-dimmed"
740
- >
741
- {{ t('requirements.empty') }}
740
+ icon="i-lucide-list-checks"
741
+ :title="t('requirements.empty')"
742
+ />
743
+
744
+ <!-- initial fetch on open -->
745
+ <div v-else-if="loading && !busy && !review" class="flex flex-col gap-3">
746
+ <USkeleton v-for="n in 3" :key="n" class="h-20 w-full rounded-lg" />
742
747
  </div>
743
748
 
744
- <!-- working state (initial fetch on open, or a reviewer pass running) -->
749
+ <!-- a reviewer pass running -->
745
750
  <div
746
- v-else-if="(busy || loading) && !review"
751
+ v-else-if="busy && !review"
747
752
  class="flex items-center justify-center gap-2 p-8 text-sm text-muted"
748
753
  >
749
- <UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
750
- {{ loading && !busy ? t('requirements.loadingReview') : t('requirements.reviewing') }}
754
+ <Spinner class="h-4 w-4" />
755
+ {{ t('requirements.reviewing') }}
751
756
  </div>
752
757
 
753
758
  <template v-else-if="review">
@@ -777,7 +782,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
777
782
  v-else-if="working"
778
783
  class="mb-4 flex items-center gap-2 rounded-lg border border-primary/60 bg-primary/10 p-4 text-sm text-primary"
779
784
  >
780
- <UIcon name="i-lucide-loader-circle" class="h-5 w-5 shrink-0 animate-spin" />
785
+ <Spinner class="h-5 w-5 shrink-0" />
781
786
  <span v-if="incorporating">
782
787
  {{ t('requirements.working.incorporating') }}
783
788
  </span>
@@ -978,7 +983,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
978
983
  v-if="pendingRecFor(item)"
979
984
  class="mt-2 flex items-center gap-1.5 text-xs text-primary"
980
985
  >
981
- <UIcon name="i-lucide-loader-circle" class="h-3.5 w-3.5 animate-spin" />
986
+ <Spinner class="h-3.5 w-3.5" />
982
987
  {{ t('requirements.generatingSuggestion') }}
983
988
  </div>
984
989
  <template v-else-if="readyRecFor(item)">
@@ -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>