@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
@@ -6,6 +6,7 @@
6
6
  import { computed } from 'vue'
7
7
  import JourneyStepNav from '~/components/environments/steps/JourneyStepNav.vue'
8
8
  import { useEnvironmentWizardTarget } from '~/modular/journeys/environmentSetup.frame'
9
+ import EmptyState from '~/components/common/EmptyState.vue'
9
10
 
10
11
  const props = defineProps<{
11
12
  input: { frameId: string | null }
@@ -48,13 +49,13 @@ const PREFLIGHT_COLOR: Record<'pass' | 'warn' | 'fail', 'success' | 'warning' |
48
49
  </UButton>
49
50
  </div>
50
51
 
51
- <p
52
+ <EmptyState
52
53
  v-if="!store.recipe.prerequisites?.length"
53
- class="text-xs text-dimmed"
54
+ compact
55
+ icon="i-lucide-list-checks"
56
+ :title="t('environmentWizard.preflight.none')"
54
57
  data-testid="env-setup-preflight-none"
55
- >
56
- {{ t('environmentWizard.preflight.none') }}
57
- </p>
58
+ />
58
59
  <p
59
60
  v-else-if="preflightsUnavailable"
60
61
  class="text-xs text-app-warning-300/80"
@@ -7,6 +7,7 @@ import PipelineProgress from '~/components/pipeline/PipelineProgress.vue'
7
7
  import IconButton from '~/components/common/IconButton.vue'
8
8
  import { useInitiativePlanning } from '~/composables/useInitiativePlanning'
9
9
  import SectionLabel from '~/components/common/SectionLabel.vue'
10
+ import EmptyState from '~/components/common/EmptyState.vue'
10
11
 
11
12
  const board = useBoardStore()
12
13
  const pipelines = usePipelinesStore()
@@ -214,12 +215,12 @@ function openApprovalFor(approvalId: string) {
214
215
  @open-approval="openApprovalFor"
215
216
  />
216
217
 
217
- <div
218
+ <EmptyState
218
219
  v-else
219
- class="flex flex-1 items-center justify-center rounded-xl border border-dashed border-muted text-sm text-dimmed"
220
- >
221
- {{ t('focus.emptyPipelineHint') }}
222
- </div>
220
+ class="flex-1"
221
+ icon="i-lucide-workflow"
222
+ :title="t('focus.emptyPipelineHint')"
223
+ />
223
224
  </section>
224
225
 
225
226
  <!-- side: details -->
@@ -13,6 +13,7 @@ import { useFollowUpsStore } from '~/stores/followUps'
13
13
  import type { FollowUpItem, FollowUpResolution } from '~/types/execution'
14
14
  import { FOLLOW_UP_COMPANION_META } from '~/utils/catalog'
15
15
  import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
16
+ import EmptyState from '~/components/common/EmptyState.vue'
16
17
 
17
18
  const execution = useExecutionStore()
18
19
  const board = useBoardStore()
@@ -156,16 +157,13 @@ function hasRecordedAnswer(item: FollowUpItem): boolean {
156
157
 
157
158
  <div class="min-h-0 flex-1 overflow-y-auto px-5 py-4">
158
159
  <!-- Empty -->
159
- <div
160
+ <EmptyState
160
161
  v-if="items.length === 0"
161
- class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-muted"
162
- >
163
- <UIcon :name="FOLLOW_UP_COMPANION_META.icon" class="h-8 w-8 opacity-40" />
164
- <p class="text-sm">{{ t('followUp.empty.title') }}</p>
165
- <p class="max-w-sm text-2xs text-dimmed">
166
- {{ t('followUp.empty.hint') }}
167
- </p>
168
- </div>
162
+ class="h-full"
163
+ :icon="FOLLOW_UP_COMPANION_META.icon"
164
+ :title="t('followUp.empty.title')"
165
+ :description="t('followUp.empty.hint')"
166
+ />
169
167
 
170
168
  <div v-else class="space-y-3">
171
169
  <p
@@ -15,6 +15,8 @@ import { useForkDecisionStore } from '~/stores/forkDecision'
15
15
  import type { ForkChatMessage, ForkDecisionStepState, ForkOption } from '~/types/execution'
16
16
  import { FORK_DECISION_META } from '~/utils/catalog'
17
17
  import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
18
+ import Spinner from '~/components/common/Spinner.vue'
19
+ import EmptyState from '~/components/common/EmptyState.vue'
18
20
 
19
21
  const execution = useExecutionStore()
20
22
  const board = useBoardStore()
@@ -170,7 +172,7 @@ const { requestClose } = useUnsavedGuard({
170
172
  v-if="status === 'proposing'"
171
173
  class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-muted"
172
174
  >
173
- <UIcon name="i-lucide-loader-circle" class="h-8 w-8 animate-spin opacity-60" />
175
+ <Spinner class="h-8 w-8 opacity-60" />
174
176
  <p class="text-sm">{{ t('forkDecision.proposing.title') }}</p>
175
177
  <p class="max-w-sm text-2xs text-dimmed">
176
178
  {{ t('forkDecision.proposing.hint') }}
@@ -322,7 +324,7 @@ const { requestClose } = useUnsavedGuard({
322
324
  <p
323
325
  class="flex items-center gap-1.5 rounded-lg bg-elevated/70 px-3 py-1.5 text-xs text-muted"
324
326
  >
325
- <UIcon name="i-lucide-loader-circle" class="h-3.5 w-3.5 animate-spin" />
327
+ <Spinner class="h-3.5 w-3.5" />
326
328
  {{ t('forkDecision.chat.thinking') }}
327
329
  </p>
328
330
  </div>
@@ -364,13 +366,12 @@ const { requestClose } = useUnsavedGuard({
364
366
  </div>
365
367
 
366
368
  <!-- Skipped / no state: nothing to decide. -->
367
- <div
369
+ <EmptyState
368
370
  v-else
369
- class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-muted"
370
- >
371
- <UIcon :name="FORK_DECISION_META.icon" class="h-8 w-8 opacity-40" />
372
- <p class="text-sm">{{ t('forkDecision.empty.title') }}</p>
373
- </div>
371
+ class="h-full"
372
+ :icon="FORK_DECISION_META.icon"
373
+ :title="t('forkDecision.empty.title')"
374
+ />
374
375
  </div>
375
376
 
376
377
  <footer
@@ -20,9 +20,10 @@ import type { ApiContractFormat, FoundationalServiceTier } from '~/types/domain'
20
20
  import { useFoundationalServicesStore } from '~/stores/foundationalServices'
21
21
  import FoundationalContractSummary from '~/components/foundational/FoundationalContractSummary.vue'
22
22
  import IconButton from '~/components/common/IconButton.vue'
23
+ import EmptyState from '~/components/common/EmptyState.vue'
23
24
 
24
25
  const catalog = useFoundationalServicesStore()
25
- const toast = useToast()
26
+ const actionToast = useActionToast()
26
27
  const { present } = usePipelineErrorToast()
27
28
  const { t } = useI18n()
28
29
 
@@ -71,7 +72,7 @@ async function suppress(serviceId: string) {
71
72
  await withRow(`suppress:${serviceId}`, async () => {
72
73
  try {
73
74
  await catalog.suppress(serviceId)
74
- toast.add({ title: t('foundational.toast.suppressed'), icon: 'i-lucide-eye-off' })
75
+ actionToast.success('foundational.toast.suppressed')
75
76
  } catch (e) {
76
77
  present(e, 'foundational.toast.suppressFailed')
77
78
  }
@@ -156,8 +157,11 @@ async function suppress(serviceId: string) {
156
157
  </div>
157
158
  </div>
158
159
 
159
- <p v-if="!catalog.resolved.length" class="text-sm text-dimmed">
160
- {{ t('foundational.catalog.empty') }}
161
- </p>
160
+ <EmptyState
161
+ v-if="!catalog.resolved.length"
162
+ compact
163
+ icon="i-lucide-package"
164
+ :title="t('foundational.catalog.empty')"
165
+ />
162
166
  </div>
163
167
  </template>
@@ -21,6 +21,7 @@ import {
21
21
  } from '~/stores/foundationalServices'
22
22
  import FoundationalContractSummary from '~/components/foundational/FoundationalContractSummary.vue'
23
23
  import IconButton from '~/components/common/IconButton.vue'
24
+ import EmptyState from '~/components/common/EmptyState.vue'
24
25
 
25
26
  const props = defineProps<{ kind: FoundationalServiceOwnerKind; ownerId: string }>()
26
27
 
@@ -28,7 +29,7 @@ const catalog =
28
29
  props.kind === 'workspace'
29
30
  ? useFoundationalServicesStore()
30
31
  : useFoundationalServices(props.kind, props.ownerId)
31
- const toast = useToast()
32
+ const actionToast = useActionToast()
32
33
  const { present } = usePipelineErrorToast()
33
34
  const { t } = useI18n()
34
35
  const { confirm } = useConfirm()
@@ -131,7 +132,7 @@ async function save() {
131
132
  capabilities: capabilityList.value,
132
133
  ...(contracts ? { contracts } : {}),
133
134
  })
134
- toast.add({ title: t('foundational.toast.created'), icon: 'i-lucide-check' })
135
+ actionToast.success('foundational.toast.created')
135
136
  } else {
136
137
  await catalog.update(draft.id, {
137
138
  name: draft.name.trim(),
@@ -140,7 +141,7 @@ async function save() {
140
141
  capabilities: capabilityList.value,
141
142
  ...(contracts ? { contracts } : {}),
142
143
  })
143
- toast.add({ title: t('foundational.toast.updated'), icon: 'i-lucide-check' })
144
+ actionToast.success('foundational.toast.updated')
144
145
  }
145
146
  editing.value = null
146
147
  } catch (e) {
@@ -171,7 +172,7 @@ async function remove(service: FoundationalService) {
171
172
  if (!ok) return
172
173
  try {
173
174
  await catalog.remove(service.id)
174
- toast.add({ title: t('foundational.toast.deleted'), icon: 'i-lucide-trash-2' })
175
+ actionToast.success('foundational.toast.deleted')
175
176
  } catch (e) {
176
177
  present(e, 'foundational.toast.deleteFailed')
177
178
  }
@@ -224,9 +225,12 @@ async function remove(service: FoundationalService) {
224
225
  </div>
225
226
  </div>
226
227
  </div>
227
- <p v-if="!catalog.services.length" class="text-sm text-dimmed">
228
- {{ t('foundational.registry.empty') }}
229
- </p>
228
+ <EmptyState
229
+ v-if="!catalog.services.length"
230
+ compact
231
+ icon="i-lucide-package"
232
+ :title="t('foundational.registry.empty')"
233
+ />
230
234
 
231
235
  <UButton
232
236
  v-if="editing === null"
@@ -28,6 +28,7 @@ import {
28
28
  import GitHubRepoSearchSelect from '~/components/github/GitHubRepoSearchSelect.vue'
29
29
  import RepoTreeBrowser from '~/components/github/RepoTreeBrowser.vue'
30
30
  import IconButton from '~/components/common/IconButton.vue'
31
+ import EmptyState from '~/components/common/EmptyState.vue'
31
32
 
32
33
  const props = defineProps<{ kind: FoundationalServiceOwnerKind; ownerId: string }>()
33
34
 
@@ -36,7 +37,7 @@ const catalog =
36
37
  ? useFoundationalServicesStore()
37
38
  : useFoundationalServices(props.kind, props.ownerId)
38
39
  const github = useGitHubStore()
39
- const toast = useToast()
40
+ const actionToast = useActionToast()
40
41
  const { present } = usePipelineErrorToast()
41
42
  const { t, d } = useI18n()
42
43
  const { confirm } = useConfirm()
@@ -163,7 +164,7 @@ async function link() {
163
164
  : {}),
164
165
  })
165
166
  resetDraft()
166
- toast.add({ title: t('foundational.toast.sourceLinked'), icon: 'i-lucide-git-branch' })
167
+ actionToast.success('foundational.toast.sourceLinked')
167
168
  return result
168
169
  } catch (e) {
169
170
  present(e, 'foundational.toast.linkSourceFailed')
@@ -185,17 +186,14 @@ async function sync(id: string) {
185
186
  )
186
187
  const coverage = result.folderScan ? folderScanNotes.value[result.folderScan] : null
187
188
  if (coverage) notes.push(coverage)
188
- toast.add({
189
- title: t('foundational.toast.synced', {
190
- updated: result.upserted,
191
- removed: result.tombstoned,
192
- }),
189
+ // A folder we could not fully see, or could not find at all, is the one sync outcome a
190
+ // human has to act on: the counts alone would read as an ordinary quiet success.
191
+ const synced = {
192
+ params: { updated: result.upserted, removed: result.tombstoned },
193
193
  ...(notes.length ? { description: notes.join(' ') } : {}),
194
- icon: 'i-lucide-refresh-cw',
195
- // A folder we could not fully see, or could not find at all, is the one sync outcome a
196
- // human has to act on — the counts alone would read as an ordinary quiet success.
197
- color: coverage ? 'warning' : 'info',
198
- })
194
+ }
195
+ if (coverage) actionToast.warning('foundational.toast.synced', synced)
196
+ else actionToast.success('foundational.toast.synced', synced)
199
197
  } catch (e) {
200
198
  present(e, 'foundational.toast.syncFailed')
201
199
  }
@@ -206,12 +204,9 @@ async function check(id: string) {
206
204
  await withRow(`check:${id}`, async () => {
207
205
  try {
208
206
  const status = await catalog.checkSource(id)
209
- toast.add({
210
- title: status.changed
211
- ? t('foundational.toast.changesAvailable')
212
- : t('foundational.toast.upToDate'),
213
- icon: status.changed ? 'i-lucide-bell-dot' : 'i-lucide-check',
214
- })
207
+ actionToast.info(
208
+ status.changed ? 'foundational.toast.changesAvailable' : 'foundational.toast.upToDate',
209
+ )
215
210
  } catch (e) {
216
211
  present(e, 'foundational.toast.checkSourceFailed')
217
212
  }
@@ -233,7 +228,7 @@ async function unlink(id: string) {
233
228
  await withRow(`unlink:${id}`, async () => {
234
229
  try {
235
230
  await catalog.unlinkSource(id)
236
- toast.add({ title: t('foundational.toast.sourceUnlinked'), icon: 'i-lucide-unplug' })
231
+ actionToast.success('foundational.toast.sourceUnlinked')
237
232
  } catch (e) {
238
233
  present(e, 'foundational.toast.unlinkSourceFailed')
239
234
  }
@@ -325,9 +320,12 @@ async function unlink(id: string) {
325
320
  />
326
321
  </div>
327
322
  </div>
328
- <p v-if="!catalog.sources.length" class="text-sm text-dimmed">
329
- {{ t('foundational.sources.empty') }}
330
- </p>
323
+ <EmptyState
324
+ v-if="!catalog.sources.length"
325
+ compact
326
+ icon="i-lucide-git-branch"
327
+ :title="t('foundational.sources.empty')"
328
+ />
331
329
 
332
330
  <!-- Linking needs the GitHub integration; say so rather than offering a form that 503s. -->
333
331
  <div
@@ -23,7 +23,7 @@ const catalog =
23
23
  props.kind === 'workspace'
24
24
  ? useFoundationalServicesStore()
25
25
  : useFoundationalServices(props.kind, props.ownerId)
26
- const toast = useToast()
26
+ const actionToast = useActionToast()
27
27
  const { present } = usePipelineErrorToast()
28
28
  const { t } = useI18n()
29
29
 
@@ -47,7 +47,7 @@ async function restore(serviceId: string) {
47
47
  busyRows.add(serviceId)
48
48
  try {
49
49
  await catalog.restore(serviceId)
50
- toast.add({ title: t('foundational.toast.restored'), icon: 'i-lucide-eye' })
50
+ actionToast.success('foundational.toast.restored')
51
51
  } catch (e) {
52
52
  present(e, 'foundational.toast.restoreFailed')
53
53
  } finally {
@@ -22,8 +22,8 @@ import {
22
22
  } from '~/utils/serviceCatalog'
23
23
 
24
24
  const catalog = useFoundationalServicesStore()
25
- const toast = useToast()
26
- const { present } = usePipelineErrorToast()
25
+ const actionToast = useActionToast()
26
+ const { present, presentReported } = usePipelineErrorToast()
27
27
  const { t, d } = useI18n()
28
28
  const { confirm } = useConfirm()
29
29
 
@@ -137,7 +137,7 @@ async function connect() {
137
137
  await withBusy('connect', async () => {
138
138
  try {
139
139
  await catalog.connectServiceCatalog(buildInput())
140
- toast.add({ title: t('serviceCatalog.toast.connected'), color: 'success' })
140
+ actionToast.success('serviceCatalog.toast.connected')
141
141
  } catch (error) {
142
142
  present(error, 'serviceCatalog.toast.connectFailed')
143
143
  return
@@ -154,13 +154,13 @@ async function probe() {
154
154
  await withBusy('probe', async () => {
155
155
  try {
156
156
  const result = await catalog.probeServiceCatalog(buildInput())
157
- toast.add({
158
- title: result.ok
159
- ? t('serviceCatalog.toast.probeOk')
160
- : t('serviceCatalog.toast.probeFailed'),
161
- description: result.message,
162
- color: result.ok ? 'success' : 'error',
163
- })
157
+ if (result.ok) {
158
+ // The probe's `message` is backend prose: on a failure it is detail behind the disclosure,
159
+ // and on a success the translated title is the whole of what the reader needs.
160
+ actionToast.success('serviceCatalog.toast.probeOk')
161
+ } else {
162
+ presentReported(result.message, 'serviceCatalog.toast.probeFailed')
163
+ }
164
164
  } catch (error) {
165
165
  present(error, 'serviceCatalog.toast.probeFailed')
166
166
  }
@@ -175,15 +175,15 @@ async function importNow() {
175
175
  async function runImport(): Promise<void> {
176
176
  try {
177
177
  const result = await catalog.importServiceCatalog()
178
- toast.add({
179
- title: t('serviceCatalog.toast.imported'),
178
+ const imported = {
180
179
  description: t('serviceCatalog.toast.importedDetail', {
181
180
  upserted: result.upserted,
182
181
  unchanged: result.unchanged,
183
182
  tombstoned: result.tombstoned,
184
183
  }),
185
- color: result.status === 'ok' ? 'success' : 'warning',
186
- })
184
+ }
185
+ if (result.status === 'ok') actionToast.success('serviceCatalog.toast.imported', imported)
186
+ else actionToast.warning('serviceCatalog.toast.imported', imported)
187
187
  } catch (error) {
188
188
  present(error, 'serviceCatalog.toast.importFailed')
189
189
  }
@@ -206,7 +206,7 @@ async function disconnect() {
206
206
  await withBusy('disconnect', async () => {
207
207
  try {
208
208
  await catalog.disconnectServiceCatalog()
209
- toast.add({ title: t('serviceCatalog.toast.disconnected'), color: 'success' })
209
+ actionToast.success('serviceCatalog.toast.disconnected')
210
210
  } catch (error) {
211
211
  present(error, 'serviceCatalog.toast.disconnectFailed')
212
212
  }
@@ -20,6 +20,7 @@ import GitHubRepoSearchSelect from '~/components/github/GitHubRepoSearchSelect.v
20
20
  import RepoTreeBrowser from '~/components/github/RepoTreeBrowser.vue'
21
21
  import GitHubDocUrlImport from '~/components/fragments/GitHubDocUrlImport.vue'
22
22
  import IconButton from '~/components/common/IconButton.vue'
23
+ import EmptyState from '~/components/common/EmptyState.vue'
23
24
 
24
25
  const props = withDefaults(
25
26
  defineProps<{
@@ -41,7 +42,7 @@ const library =
41
42
  : useFragmentLibrary(props.kind, props.ownerId)
42
43
  const documents = useDocumentsStore()
43
44
  const github = useGitHubStore()
44
- const toast = useToast()
45
+ const actionToast = useActionToast()
45
46
  const { present } = usePipelineErrorToast()
46
47
  const { t, d } = useI18n()
47
48
  const { confirm } = useConfirm()
@@ -207,7 +208,7 @@ async function saveEdit() {
207
208
  .filter(Boolean),
208
209
  })
209
210
  editDraft.value = null
210
- toast.add({ title: t('fragments.toast.updated'), icon: 'i-lucide-check' })
211
+ actionToast.success('fragments.toast.updated')
211
212
  } catch (e) {
212
213
  present(e, 'fragments.toast.updateFailed')
213
214
  }
@@ -229,7 +230,7 @@ async function createFragment() {
229
230
  .filter(Boolean),
230
231
  })
231
232
  draft.value = { title: '', summary: '', body: '', brief: '', tags: '' }
232
- toast.add({ title: t('fragments.toast.added'), icon: 'i-lucide-check' })
233
+ actionToast.success('fragments.toast.added')
233
234
  } catch (e) {
234
235
  present(e, 'fragments.toast.addFailed')
235
236
  } finally {
@@ -250,7 +251,7 @@ async function removeFragment(id: string) {
250
251
  await withRow(`remove:${id}`, async () => {
251
252
  try {
252
253
  await library.remove(id)
253
- toast.add({ title: t('fragments.toast.removed'), icon: 'i-lucide-trash-2' })
254
+ actionToast.success('fragments.toast.removed')
254
255
  } catch (e) {
255
256
  present(e, 'fragments.toast.removeFailed')
256
257
  }
@@ -415,7 +416,7 @@ async function linkDocumentFragment() {
415
416
  // Free-text field: exactly one ref.
416
417
  await library.createDocumentFragment({ source, ref: docDraft.value.ref.trim(), tags })
417
418
  docDraft.value = { source: '', ref: '', tags: '' }
418
- toast.add({ title: t('fragments.toast.documentLinked'), icon: 'i-lucide-link' })
419
+ actionToast.success('fragments.toast.documentLinked')
419
420
  return
420
421
  }
421
422
 
@@ -440,9 +441,9 @@ async function linkDocumentFragment() {
440
441
  if (linked > 0) {
441
442
  // A vue-i18n plural message (count as the plural choice) so Slavic few/many forms
442
443
  // render correctly, and the count==1 case reads naturally too.
443
- toast.add({
444
- title: t('fragments.toast.documentsLinked', { count: linked }, linked),
445
- icon: 'i-lucide-link',
444
+ actionToast.success('fragments.toast.documentsLinked', {
445
+ params: { count: linked },
446
+ plural: linked,
446
447
  })
447
448
  }
448
449
  if (firstError) {
@@ -462,7 +463,7 @@ async function refreshFragment(id: string) {
462
463
  await withRow(`refresh:${id}`, async () => {
463
464
  try {
464
465
  await library.refreshDocumentFragment(id)
465
- toast.add({ title: t('fragments.toast.refreshed'), icon: 'i-lucide-refresh-cw' })
466
+ actionToast.success('fragments.toast.refreshed')
466
467
  } catch (e) {
467
468
  present(e, 'fragments.toast.refreshFailed')
468
469
  }
@@ -519,7 +520,7 @@ async function linkSource() {
519
520
  // `syncSource` row wrapper): a failure here should surface as a link failure, and
520
521
  // the form-level `linkingSource` spinner already covers the whole operation.
521
522
  await library.syncSource(source.id)
522
- toast.add({ title: t('fragments.toast.sourceLinked'), icon: 'i-lucide-git-branch' })
523
+ actionToast.success('fragments.toast.sourceLinked')
523
524
  } catch (e) {
524
525
  present(e, 'fragments.toast.linkSourceFailed')
525
526
  } finally {
@@ -531,13 +532,8 @@ async function syncSource(id: string) {
531
532
  await withRow(`sync:${id}`, async () => {
532
533
  try {
533
534
  const result = await library.syncSource(id)
534
- toast.add({
535
- title: t('fragments.toast.synced', {
536
- updated: result.upserted,
537
- removed: result.tombstoned,
538
- }),
539
- icon: 'i-lucide-refresh-cw',
540
- color: 'info',
535
+ actionToast.success('fragments.toast.synced', {
536
+ params: { updated: result.upserted, removed: result.tombstoned },
541
537
  })
542
538
  } catch (e) {
543
539
  present(e, 'fragments.toast.syncFailed')
@@ -549,12 +545,9 @@ async function checkSource(id: string) {
549
545
  await withRow(`check:${id}`, async () => {
550
546
  try {
551
547
  const status = await library.checkSource(id)
552
- toast.add({
553
- title: status.changed
554
- ? t('fragments.toast.changesAvailable')
555
- : t('fragments.toast.upToDate'),
556
- icon: status.changed ? 'i-lucide-bell-dot' : 'i-lucide-check',
557
- })
548
+ actionToast.info(
549
+ status.changed ? 'fragments.toast.changesAvailable' : 'fragments.toast.upToDate',
550
+ )
558
551
  } catch (e) {
559
552
  present(e, 'fragments.toast.checkSourceFailed')
560
553
  }
@@ -575,7 +568,7 @@ async function unlinkSource(id: string) {
575
568
  await withRow(`unlink:${id}`, async () => {
576
569
  try {
577
570
  await library.unlinkSource(id)
578
- toast.add({ title: t('fragments.toast.sourceUnlinked'), icon: 'i-lucide-unplug' })
571
+ actionToast.success('fragments.toast.sourceUnlinked')
579
572
  } catch (e) {
580
573
  present(e, 'fragments.toast.unlinkSourceFailed')
581
574
  }
@@ -771,13 +764,16 @@ async function unlinkSource(id: string) {
771
764
  </div>
772
765
  </div>
773
766
  </div>
774
- <p v-if="!library.fragments.length" class="text-sm text-dimmed">
775
- {{
767
+ <EmptyState
768
+ v-if="!library.fragments.length"
769
+ compact
770
+ icon="i-lucide-puzzle"
771
+ :title="
776
772
  isWorkspace
777
773
  ? t('fragments.authored.empty.workspace')
778
774
  : t('fragments.authored.empty.account')
779
- }}
780
- </p>
775
+ "
776
+ />
781
777
 
782
778
  <div class="rounded-md border border-default p-3">
783
779
  <p class="mb-2 text-sm font-medium">{{ t('fragments.authored.addTitle') }}</p>
@@ -887,9 +883,12 @@ async function unlinkSource(id: string) {
887
883
  />
888
884
  </div>
889
885
  </div>
890
- <p v-if="!documentFragments.length" class="text-sm text-dimmed">
891
- {{ t('fragments.documents.empty') }}
892
- </p>
886
+ <EmptyState
887
+ v-if="!documentFragments.length"
888
+ compact
889
+ icon="i-lucide-file-text"
890
+ :title="t('fragments.documents.empty')"
891
+ />
893
892
 
894
893
  <div class="rounded-md border border-default p-3">
895
894
  <p class="mb-2 text-sm font-medium">{{ t('fragments.documents.linkTitle') }}</p>
@@ -1053,9 +1052,12 @@ async function unlinkSource(id: string) {
1053
1052
  />
1054
1053
  </div>
1055
1054
  </div>
1056
- <p v-if="!library.sources.length" class="text-sm text-dimmed">
1057
- {{ t('fragments.sources.empty') }}
1058
- </p>
1055
+ <EmptyState
1056
+ v-if="!library.sources.length"
1057
+ compact
1058
+ icon="i-lucide-git-branch"
1059
+ :title="t('fragments.sources.empty')"
1060
+ />
1059
1061
 
1060
1062
  <div class="rounded-md border border-default p-3">
1061
1063
  <p class="mb-2 text-sm font-medium">{{ t('fragments.sources.linkTitle') }}</p>
@@ -11,6 +11,7 @@
11
11
  import type { PromptFragment } from '~/types/domain'
12
12
  import { buildFragmentCategoryGroups } from '~/utils/fragmentPicker'
13
13
  import SectionLabel from '~/components/common/SectionLabel.vue'
14
+ import EmptyState from '~/components/common/EmptyState.vue'
14
15
 
15
16
  const props = withDefaults(
16
17
  defineProps<{
@@ -123,9 +124,12 @@ function manageAccount() {
123
124
  </UButton>
124
125
  </div>
125
126
  </template>
126
- <p v-else class="px-2 py-3 text-xs text-dimmed">
127
- {{ t('inspector.fragments.pickerEmpty') }}
128
- </p>
127
+ <EmptyState
128
+ v-else
129
+ compact
130
+ icon="i-lucide-puzzle"
131
+ :title="t('inspector.fragments.pickerEmpty')"
132
+ />
129
133
 
130
134
  <div class="mt-1 border-t border-default pt-1">
131
135
  <UButton
@@ -14,6 +14,7 @@ import AttemptEntryHeader from '~/components/panels/AttemptEntryHeader.vue'
14
14
  import GateFailingCheckList from '~/components/gates/GateFailingCheckList.vue'
15
15
  import CopyButton from '~/components/common/CopyButton.vue'
16
16
  import SectionLabel from '~/components/common/SectionLabel.vue'
17
+ import EmptyState from '~/components/common/EmptyState.vue'
17
18
 
18
19
  const board = useBoardStore()
19
20
  const execution = useExecutionStore()
@@ -213,16 +214,12 @@ const conflictVerdict = computed(() => {
213
214
  <div class="flex min-h-0 flex-1">
214
215
  <!-- Main: the conclusion -->
215
216
  <div class="min-w-0 flex-1 overflow-y-auto px-5 py-4">
216
- <div
217
+ <EmptyState
217
218
  v-if="!gate"
218
- class="flex h-full flex-col items-center justify-center gap-2 text-center text-muted"
219
- >
220
- <UIcon :name="meta.icon" class="h-8 w-8 opacity-40" />
221
- <p class="text-sm">{{ t('gates.noActivity') }}</p>
222
- <p class="max-w-sm text-2xs text-dimmed">
223
- {{ t('gates.noActivityHint') }}
224
- </p>
225
- </div>
219
+ :icon="meta.icon"
220
+ :title="t('gates.noActivity')"
221
+ :description="t('gates.noActivityHint')"
222
+ />
226
223
 
227
224
  <template v-else>
228
225
  <!-- Passed -->