@cat-factory/app 0.261.2 → 0.261.4

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 (90) hide show
  1. package/README.md +40 -0
  2. package/app/components/board/AddTaskModal.vue +10 -6
  3. package/app/components/board/AgentStopButton.vue +2 -5
  4. package/app/components/board/CreateInitiativeModal.vue +2 -7
  5. package/app/components/board/RecurringPipelineModal.vue +10 -6
  6. package/app/components/bootstrap/BootstrapModal.vue +5 -24
  7. package/app/components/brainstorm/BrainstormWindow.vue +7 -15
  8. package/app/components/clarity/ClarityReviewWindow.vue +7 -15
  9. package/app/components/documents/DocumentImportModal.vue +2 -6
  10. package/app/components/documents/DocumentSourceConnectModal.vue +3 -12
  11. package/app/components/documents/DocumentSyncState.vue +2 -7
  12. package/app/components/documents/DocumentTemplatesModal.vue +4 -19
  13. package/app/components/documents/SpawnPreviewModal.vue +3 -12
  14. package/app/components/foundational/FoundationalServiceCatalogList.vue +3 -11
  15. package/app/components/foundational/FoundationalServiceRegistry.vue +3 -11
  16. package/app/components/foundational/FoundationalServiceSources.vue +5 -13
  17. package/app/components/foundational/FoundationalSuppressions.vue +2 -6
  18. package/app/components/fragments/FragmentLibraryManager.vue +12 -20
  19. package/app/components/github/AddServiceFromRepoModal.vue +3 -12
  20. package/app/components/github/GitHubConnect.vue +4 -12
  21. package/app/components/github/GitHubPanel.vue +9 -17
  22. package/app/components/github/RepoTreeBrowser.vue +2 -7
  23. package/app/components/humanTest/HumanTestWindow.vue +2 -7
  24. package/app/components/initiative/InitiativeTrackerWindow.vue +2 -3
  25. package/app/components/layout/AccountAuditLog.vue +2 -7
  26. package/app/components/layout/AccountDeploymentSettings.vue +11 -51
  27. package/app/components/layout/AccountModelPolicySettings.vue +2 -5
  28. package/app/components/layout/AccountPlatformAlertSettings.vue +2 -5
  29. package/app/components/layout/AccountRunCredentialSettings.vue +2 -5
  30. package/app/components/layout/AccountTeamSettings.vue +9 -18
  31. package/app/components/layout/BoardSwitcher.vue +6 -14
  32. package/app/components/layout/BoardToolbar.vue +3 -10
  33. package/app/components/layout/NotificationsInbox.vue +3 -13
  34. package/app/components/layout/WorkspaceMembersSettings.vue +6 -15
  35. package/app/components/notifications/NotificationSettingsPanel.vue +3 -11
  36. package/app/components/panels/DecisionModal.vue +2 -7
  37. package/app/components/panels/inspector/RecurringScheduleSettings.vue +4 -20
  38. package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +3 -11
  39. package/app/components/panels/inspector/ServiceTestSecrets.vue +3 -11
  40. package/app/components/panels/inspector/ServiceValidationConfig.vue +4 -12
  41. package/app/components/pipeline/PipelineBuilder.vue +4 -7
  42. package/app/components/providers/ApiKeysSection.vue +4 -15
  43. package/app/components/providers/PersonalCredentialModal.vue +2 -5
  44. package/app/components/providers/PersonalSubscriptionSection.vue +3 -10
  45. package/app/components/providers/VendorCredentialsModal.vue +5 -20
  46. package/app/components/requirements/RequirementsReviewWindow.vue +11 -19
  47. package/app/components/sandbox/SandboxPanel.vue +4 -18
  48. package/app/components/settings/ApiTokensPanel.vue +4 -12
  49. package/app/components/settings/BudgetSettings.vue +2 -5
  50. package/app/components/settings/CloudflareHandlerSection.vue +3 -12
  51. package/app/components/settings/ConsensusGroupsSection.vue +3 -12
  52. package/app/components/settings/CustomManifestTypeEditor.vue +3 -13
  53. package/app/components/settings/DefaultProvisionTypeSection.vue +2 -5
  54. package/app/components/settings/InfraHandlersConfigurator.vue +7 -14
  55. package/app/components/settings/InfrastructureBackendPicker.vue +2 -7
  56. package/app/components/settings/IssueTrackerPanel.vue +4 -18
  57. package/app/components/settings/LocalContainerPoolSettings.vue +2 -6
  58. package/app/components/settings/LocalModelEndpointsPanel.vue +3 -11
  59. package/app/components/settings/ModelConfigurationPanel.vue +13 -16
  60. package/app/components/settings/ModelPresetHealthModal.vue +3 -13
  61. package/app/components/settings/ObservabilityConnectionPanel.vue +6 -14
  62. package/app/components/settings/OpenRouterCatalogPanel.vue +3 -12
  63. package/app/components/settings/PackageRegistriesPanel.vue +4 -12
  64. package/app/components/settings/ProviderConnectionTab.vue +5 -13
  65. package/app/components/settings/RiskPolicyHealthModal.vue +2 -7
  66. package/app/components/settings/RiskPolicyPanel.vue +5 -13
  67. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +2 -7
  68. package/app/components/settings/SharedStacksPanel.vue +10 -20
  69. package/app/components/settings/TaskTypeSuppressionsPanel.vue +3 -12
  70. package/app/components/settings/UserSecretsSection.vue +3 -11
  71. package/app/components/settings/WorkspaceMetadataSettings.vue +2 -6
  72. package/app/components/settings/WorkspaceSettingsPanel.vue +2 -6
  73. package/app/components/skills/SkillLibraryManager.vue +5 -13
  74. package/app/components/slack/SlackPanel.vue +7 -15
  75. package/app/components/tasks/BugHuntModal.vue +2 -6
  76. package/app/components/tasks/TaskImportModal.vue +2 -6
  77. package/app/components/tasks/TaskSourceConnectModal.vue +4 -18
  78. package/app/composables/useBlockDeletion.ts +2 -6
  79. package/app/composables/useCopyToClipboard.ts +7 -1
  80. package/app/composables/usePipelineErrorToast.spec.ts +76 -16
  81. package/app/composables/usePipelineErrorToast.ts +109 -50
  82. package/app/stores/board/context.ts +9 -2
  83. package/app/stores/board/dependencies.ts +3 -13
  84. package/app/stores/board/mutations.ts +2 -7
  85. package/app/stores/board/placement.ts +12 -25
  86. package/app/stores/board/removal.ts +2 -7
  87. package/app/stores/board.spec.ts +15 -8
  88. package/app/stores/board.ts +11 -1
  89. package/app/stores/recurringPipelines.ts +5 -11
  90. package/package.json +2 -2
@@ -32,6 +32,7 @@ const agents = useAgentsStore()
32
32
  const agentTier = useAgentTierStore()
33
33
  const creds = useVendorCredentialsStore()
34
34
  const workspace = useWorkspaceStore()
35
+ const { present } = usePipelineErrorToast()
35
36
  const toast = useToast()
36
37
  const { confirm } = useConfirm()
37
38
 
@@ -180,7 +181,7 @@ async function setDefault(p: ModelPreset) {
180
181
  try {
181
182
  await presets.update(p.id, { isDefault: true })
182
183
  } catch (e) {
183
- fail(t('settings.modelConfiguration.toast.setDefaultFailed'), e)
184
+ present(e, 'settings.modelConfiguration.toast.setDefaultFailed')
184
185
  } finally {
185
186
  busy.value = false
186
187
  }
@@ -199,7 +200,7 @@ async function remove(p: ModelPreset) {
199
200
  try {
200
201
  await presets.remove(p.id)
201
202
  } catch (e) {
202
- fail(t('settings.modelConfiguration.toast.deleteFailed'), e)
203
+ present(e, 'settings.modelConfiguration.toast.deleteFailed')
203
204
  } finally {
204
205
  busy.value = false
205
206
  }
@@ -251,10 +252,15 @@ async function save() {
251
252
  const e = editor.value
252
253
  if (!e) return
253
254
  if (!e.name.trim()) {
254
- fail(
255
- t('settings.modelConfiguration.toast.nameRequiredTitle'),
256
- new Error(t('settings.modelConfiguration.toast.nameRequiredBody')),
257
- )
255
+ // NOT through `present`: that funnel classifies a BACKEND failure, and a synthesized local
256
+ // `Error` carries no envelope and no status, so it lands on the network-fault description and
257
+ // tells the user the server could not be reached about a check that never left the browser.
258
+ toast.add({
259
+ title: t('settings.modelConfiguration.toast.nameRequiredTitle'),
260
+ description: t('settings.modelConfiguration.toast.nameRequiredBody'),
261
+ color: 'warning',
262
+ icon: 'i-lucide-triangle-alert',
263
+ })
258
264
  return
259
265
  }
260
266
  busy.value = true
@@ -280,20 +286,11 @@ async function save() {
280
286
  }
281
287
  editor.value = null
282
288
  } catch (err) {
283
- fail(t('settings.modelConfiguration.toast.saveFailed'), err)
289
+ present(err, 'settings.modelConfiguration.toast.saveFailed')
284
290
  } finally {
285
291
  busy.value = false
286
292
  }
287
293
  }
288
-
289
- function fail(title: string, e: unknown) {
290
- toast.add({
291
- title,
292
- description: e instanceof Error ? e.message : String(e),
293
- icon: 'i-lucide-triangle-alert',
294
- color: 'error',
295
- })
296
- }
297
294
  </script>
298
295
 
299
296
  <template>
@@ -9,7 +9,7 @@ const { t } = useI18n()
9
9
  const ui = useUiStore()
10
10
  const presets = useModelPresetsStore()
11
11
  const { newPresets, outdated, hasIssues } = useModelPresetHealth()
12
- const toast = useToast()
12
+ const { present } = usePipelineErrorToast()
13
13
 
14
14
  const open = computed({
15
15
  get: () => ui.modelPresetHealthOpen,
@@ -28,12 +28,7 @@ async function reseed(id: string) {
28
28
  try {
29
29
  await presets.reseed(id)
30
30
  } catch (e) {
31
- toast.add({
32
- title: t('modelPreset.health.toast.reseedFailed'),
33
- description: e instanceof Error ? e.message : String(e),
34
- icon: 'i-lucide-triangle-alert',
35
- color: 'error',
36
- })
31
+ present(e, 'modelPreset.health.toast.reseedFailed')
37
32
  } finally {
38
33
  const next = new Set(busy.value)
39
34
  next.delete(id)
@@ -48,12 +43,7 @@ async function reseedAll() {
48
43
  try {
49
44
  await presets.reseedMany(ids)
50
45
  } catch (e) {
51
- toast.add({
52
- title: t('modelPreset.health.toast.reseedFailed'),
53
- description: e instanceof Error ? e.message : String(e),
54
- icon: 'i-lucide-triangle-alert',
55
- color: 'error',
56
- })
46
+ present(e, 'modelPreset.health.toast.reseedFailed')
57
47
  } finally {
58
48
  const next = new Set(busy.value)
59
49
  for (const id of ids) next.delete(id)
@@ -13,6 +13,7 @@ const { t } = useI18n()
13
13
  const ui = useUiStore()
14
14
  const store = useReleaseHealthStore()
15
15
  const toast = useToast()
16
+ const { present } = usePipelineErrorToast()
16
17
  const { confirmAction, toastDone } = useConfirmAction()
17
18
 
18
19
  const open = computed({
@@ -36,15 +37,6 @@ const pagerDuty = reactive({ apiToken: '', fromEmail: '' })
36
37
  const incidentIo = reactive({ apiKey: '' })
37
38
  const incidentBusy = ref(false)
38
39
 
39
- function notifyError(title: string, e: unknown) {
40
- toast.add({
41
- title,
42
- description: e instanceof Error ? e.message : String(e),
43
- icon: 'i-lucide-triangle-alert',
44
- color: 'error',
45
- })
46
- }
47
-
48
40
  watch(
49
41
  open,
50
42
  async (isOpen) => {
@@ -56,7 +48,7 @@ watch(
56
48
  if (site) datadog.site = site
57
49
  await store.loadIncident()
58
50
  } catch (e) {
59
- notifyError(t('settings.observabilityConnection.toast.loadFailed'), e)
51
+ present(e, 'settings.observabilityConnection.toast.loadFailed')
60
52
  }
61
53
  },
62
54
  { immediate: true },
@@ -90,7 +82,7 @@ async function saveIncident() {
90
82
  color: 'success',
91
83
  })
92
84
  } catch (e) {
93
- notifyError(t('settings.observabilityConnection.toast.incidentSaveFailed'), e)
85
+ present(e, 'settings.observabilityConnection.toast.incidentSaveFailed')
94
86
  } finally {
95
87
  incidentBusy.value = false
96
88
  }
@@ -104,7 +96,7 @@ async function disconnectIncident() {
104
96
  await store.removeIncident()
105
97
  toastDone('disconnect', noun)
106
98
  } catch (e) {
107
- notifyError(t('settings.observabilityConnection.toast.incidentDisconnectFailed'), e)
99
+ present(e, 'settings.observabilityConnection.toast.incidentDisconnectFailed')
108
100
  } finally {
109
101
  incidentBusy.value = false
110
102
  }
@@ -125,7 +117,7 @@ async function saveConnection() {
125
117
  color: 'success',
126
118
  })
127
119
  } catch (e) {
128
- notifyError(t('settings.observabilityConnection.toast.connectFailed'), e)
120
+ present(e, 'settings.observabilityConnection.toast.connectFailed')
129
121
  } finally {
130
122
  busy.value = false
131
123
  }
@@ -139,7 +131,7 @@ async function disconnect() {
139
131
  await store.removeConnection()
140
132
  toastDone('disconnect', noun)
141
133
  } catch (e) {
142
- notifyError(t('settings.observabilityConnection.toast.disconnectFailed'), e)
134
+ present(e, 'settings.observabilityConnection.toast.disconnectFailed')
143
135
  } finally {
144
136
  busy.value = false
145
137
  }
@@ -19,6 +19,7 @@ const store = useOpenRouterStore()
19
19
  const apiKeys = useApiKeysStore()
20
20
  const models = useModelsStore()
21
21
  const toast = useToast()
22
+ const { present } = usePipelineErrorToast()
22
23
 
23
24
  const open = computed({
24
25
  get: () => ui.openRouterOpen,
@@ -158,12 +159,7 @@ async function connectKey() {
158
159
  color: 'success',
159
160
  })
160
161
  } catch (e) {
161
- toast.add({
162
- title: t('settings.openRouterCatalog.toast.connectFailed'),
163
- description: e instanceof Error ? e.message : String(e),
164
- icon: 'i-lucide-triangle-alert',
165
- color: 'error',
166
- })
162
+ present(e, 'settings.openRouterCatalog.toast.connectFailed')
167
163
  } finally {
168
164
  connectingKey.value = false
169
165
  }
@@ -200,12 +196,7 @@ async function save() {
200
196
  color: 'success',
201
197
  })
202
198
  } catch (e) {
203
- toast.add({
204
- title: t('settings.openRouterCatalog.toast.saveFailed'),
205
- description: e instanceof Error ? e.message : String(e),
206
- icon: 'i-lucide-triangle-alert',
207
- color: 'error',
208
- })
199
+ present(e, 'settings.openRouterCatalog.toast.saveFailed')
209
200
  } finally {
210
201
  busy.value = false
211
202
  }
@@ -13,6 +13,7 @@ import SecretInput from '~/components/common/SecretInput.vue'
13
13
  const { t } = useI18n()
14
14
  const store = usePackageRegistriesStore()
15
15
  const toast = useToast()
16
+ const { present } = usePipelineErrorToast()
16
17
  const { confirmAction, toastDone } = useConfirmAction()
17
18
 
18
19
  // The registry vendors a workspace can connect. Fixed set — the host derives from the
@@ -44,15 +45,6 @@ const parsedScopes = computed(() =>
44
45
  .map((s) => (s.startsWith('@') ? s : `@${s}`)),
45
46
  )
46
47
 
47
- function notifyError(title: string, e: unknown) {
48
- toast.add({
49
- title,
50
- description: e instanceof Error ? e.message : String(e),
51
- icon: 'i-lucide-triangle-alert',
52
- color: 'error',
53
- })
54
- }
55
-
56
48
  // The tab this renders in only exists once the window's probe resolved `available === true`, so
57
49
  // `ensureLoaded()` here would early-return every time and this error branch would be dead code.
58
50
  // Read the list outright instead: the window owns the PROBE (a failure there means no tab), the
@@ -63,7 +55,7 @@ onMounted(async () => {
63
55
  try {
64
56
  await store.load()
65
57
  } catch (e) {
66
- notifyError(t('settings.packageRegistries.toast.loadFailed'), e)
58
+ present(e, 'settings.packageRegistries.toast.loadFailed')
67
59
  }
68
60
  })
69
61
 
@@ -84,7 +76,7 @@ async function addEntry() {
84
76
  color: 'success',
85
77
  })
86
78
  } catch (e) {
87
- notifyError(t('settings.packageRegistries.toast.addFailed'), e)
79
+ present(e, 'settings.packageRegistries.toast.addFailed')
88
80
  } finally {
89
81
  busy.value = false
90
82
  }
@@ -98,7 +90,7 @@ async function removeEntry(entryId: string) {
98
90
  await store.remove(entryId)
99
91
  toastDone('remove', noun)
100
92
  } catch (e) {
101
- notifyError(t('settings.packageRegistries.toast.removeFailed'), e)
93
+ present(e, 'settings.packageRegistries.toast.removeFailed')
102
94
  } finally {
103
95
  busy.value = false
104
96
  }
@@ -36,6 +36,7 @@ const emit = defineEmits<{ connected: [] }>()
36
36
  const { t } = useI18n()
37
37
  const store = useProviderConnectionsStore()
38
38
  const toast = useToast()
39
+ const { present } = usePipelineErrorToast()
39
40
  const { confirmAction } = useConfirmAction()
40
41
 
41
42
  const descriptor = computed(() => store.descriptorFor(props.kind))
@@ -205,15 +206,6 @@ function buildFlatPayload() {
205
206
  return isNativeConfig.value ? buildConfigPayload() : buildManifestPayload()
206
207
  }
207
208
 
208
- function notifyError(title: string, e: unknown) {
209
- toast.add({
210
- title,
211
- description: e instanceof Error ? e.message : String(e),
212
- icon: 'i-lucide-triangle-alert',
213
- color: 'error',
214
- })
215
- }
216
-
217
209
  function toastSaved() {
218
210
  toast.add({
219
211
  title: t('settings.providerConnection.toast.saved', { title: title.value }),
@@ -246,7 +238,7 @@ async function saveNative() {
246
238
  resetDraft()
247
239
  toastSaved()
248
240
  } catch (e) {
249
- notifyError(t('settings.providerConnection.toast.saveFailed'), e)
241
+ present(e, 'settings.providerConnection.toast.saveFailed')
250
242
  } finally {
251
243
  busy.value = false
252
244
  }
@@ -280,7 +272,7 @@ async function saveManifest(payload: {
280
272
  emit('connected')
281
273
  toastSaved()
282
274
  } catch (e) {
283
- notifyError(t('settings.providerConnection.toast.saveFailed'), e)
275
+ present(e, 'settings.providerConnection.toast.saveFailed')
284
276
  } finally {
285
277
  busy.value = false
286
278
  }
@@ -325,7 +317,7 @@ async function saveConfig(payload: {
325
317
  emit('connected')
326
318
  toastSaved()
327
319
  } catch (e) {
328
- notifyError(t('settings.providerConnection.toast.saveFailed'), e)
320
+ present(e, 'settings.providerConnection.toast.saveFailed')
329
321
  } finally {
330
322
  busy.value = false
331
323
  }
@@ -339,7 +331,7 @@ async function remove() {
339
331
  resetDraft()
340
332
  toast.add({ title: t('settings.providerConnection.toast.removed'), icon: 'i-lucide-check' })
341
333
  } catch (e) {
342
- notifyError(t('settings.providerConnection.toast.removeFailed'), e)
334
+ present(e, 'settings.providerConnection.toast.removeFailed')
343
335
  } finally {
344
336
  busy.value = false
345
337
  }
@@ -9,7 +9,7 @@ const { t } = useI18n()
9
9
  const ui = useUiStore()
10
10
  const presets = useRiskPoliciesStore()
11
11
  const { newPresets, outdated, hasIssues } = useRiskPolicyHealth()
12
- const toast = useToast()
12
+ const { present } = usePipelineErrorToast()
13
13
 
14
14
  const open = computed({
15
15
  get: () => ui.riskPolicyHealthOpen,
@@ -28,12 +28,7 @@ async function reseed(id: string) {
28
28
  try {
29
29
  await presets.reseed(id)
30
30
  } catch (e) {
31
- toast.add({
32
- title: t('riskPolicy.health.toast.reseedFailed'),
33
- description: e instanceof Error ? e.message : String(e),
34
- icon: 'i-lucide-triangle-alert',
35
- color: 'error',
36
- })
31
+ present(e, 'riskPolicy.health.toast.reseedFailed')
37
32
  } finally {
38
33
  const next = new Set(busy.value)
39
34
  next.delete(id)
@@ -70,6 +70,7 @@ const CONCERN_LEVELS = computed<{ value: RequirementConcernLevel; label: string
70
70
 
71
71
  const store = useRiskPoliciesStore()
72
72
  const toast = useToast()
73
+ const { present } = usePipelineErrorToast()
73
74
  const { confirm } = useConfirm()
74
75
 
75
76
  // Local editable copy per preset, kept in sync with the store. Percentages are
@@ -153,15 +154,6 @@ watch(
153
154
 
154
155
  const busy = ref<string | null>(null)
155
156
 
156
- function notifyError(title: string, e: unknown) {
157
- toast.add({
158
- title,
159
- description: e instanceof Error ? e.message : String(e),
160
- icon: 'i-lucide-triangle-alert',
161
- color: 'error',
162
- })
163
- }
164
-
165
157
  async function save(p: RiskPolicy) {
166
158
  const d = drafts[p.id]
167
159
  if (!d) return
@@ -188,7 +180,7 @@ async function save(p: RiskPolicy) {
188
180
  color: 'success',
189
181
  })
190
182
  } catch (e) {
191
- notifyError(t('settings.riskPolicy.toast.saveFailed'), e)
183
+ present(e, 'settings.riskPolicy.toast.saveFailed')
192
184
  } finally {
193
185
  busy.value = null
194
186
  }
@@ -199,7 +191,7 @@ async function makeDefault(p: RiskPolicy) {
199
191
  try {
200
192
  await store.update(p.id, { isDefault: true })
201
193
  } catch (e) {
202
- notifyError(t('settings.riskPolicy.toast.defaultFailed'), e)
194
+ present(e, 'settings.riskPolicy.toast.defaultFailed')
203
195
  } finally {
204
196
  busy.value = null
205
197
  }
@@ -218,7 +210,7 @@ async function remove(p: RiskPolicy) {
218
210
  try {
219
211
  await store.remove(p.id)
220
212
  } catch (e) {
221
- notifyError(t('settings.riskPolicy.toast.deleteFailed'), e)
213
+ present(e, 'settings.riskPolicy.toast.deleteFailed')
222
214
  } finally {
223
215
  busy.value = null
224
216
  }
@@ -274,7 +266,7 @@ async function create() {
274
266
  color: 'success',
275
267
  })
276
268
  } catch (e) {
277
- notifyError(t('settings.riskPolicy.toast.createFailed'), e)
269
+ present(e, 'settings.riskPolicy.toast.createFailed')
278
270
  } finally {
279
271
  creating.value = false
280
272
  }
@@ -12,7 +12,7 @@ const { t } = useI18n()
12
12
  const fragments = useFragmentsStore()
13
13
  const defaults = useServiceFragmentDefaultsStore()
14
14
  const ui = useUiStore()
15
- const toast = useToast()
15
+ const { present } = usePipelineErrorToast()
16
16
 
17
17
  const busy = ref(false)
18
18
 
@@ -36,12 +36,7 @@ async function save(ids: string[]) {
36
36
  try {
37
37
  await defaults.set(ids)
38
38
  } catch (e) {
39
- toast.add({
40
- title: t('settings.serviceFragmentDefaults.saveFailed'),
41
- description: e instanceof Error ? e.message : String(e),
42
- icon: 'i-lucide-triangle-alert',
43
- color: 'error',
44
- })
39
+ present(e, 'settings.serviceFragmentDefaults.saveFailed')
45
40
  } finally {
46
41
  busy.value = false
47
42
  }
@@ -16,6 +16,7 @@ import type {
16
16
  const { t } = useI18n()
17
17
  const store = useSharedStacksStore()
18
18
  const toast = useToast()
19
+ const { present } = usePipelineErrorToast()
19
20
  const { confirmAction, toastDone } = useConfirmAction()
20
21
 
21
22
  const stacks = computed(() => store.stacks)
@@ -169,21 +170,12 @@ async function autodetect() {
169
170
  color: 'success',
170
171
  })
171
172
  } catch (e) {
172
- notifyError(t('settings.sharedStacks.detect.failed'), e)
173
+ present(e, 'settings.sharedStacks.detect.failed')
173
174
  } finally {
174
175
  detecting.value = false
175
176
  }
176
177
  }
177
178
 
178
- function notifyError(title: string, e: unknown) {
179
- toast.add({
180
- title,
181
- description: e instanceof Error ? e.message : String(e),
182
- icon: 'i-lucide-triangle-alert',
183
- color: 'error',
184
- })
185
- }
186
-
187
179
  /** Create a new stack, or save edits to the one being edited (same form, mode toggled by `editingId`). */
188
180
  async function saveStack() {
189
181
  saving.value = true
@@ -223,13 +215,11 @@ async function saveStack() {
223
215
  color: 'success',
224
216
  })
225
217
  } catch (e) {
226
- notifyError(
227
- t(
228
- editing
229
- ? 'settings.sharedStacks.toast.updateFailed'
230
- : 'settings.sharedStacks.toast.createFailed',
231
- ),
218
+ present(
232
219
  e,
220
+ editing
221
+ ? 'settings.sharedStacks.toast.updateFailed'
222
+ : 'settings.sharedStacks.toast.createFailed',
233
223
  )
234
224
  } finally {
235
225
  saving.value = false
@@ -243,10 +233,10 @@ async function start(stack: SharedStack) {
243
233
  // surface that as an error toast too — not only a thrown transport/unavailable error.
244
234
  const updated = await store.ensureUp(stack.id)
245
235
  if (updated.status === 'failed') {
246
- notifyError(t('settings.sharedStacks.toast.startFailed'), updated.lastError ?? '')
236
+ present(updated.lastError ?? '', 'settings.sharedStacks.toast.startFailed')
247
237
  }
248
238
  } catch (e) {
249
- notifyError(t('settings.sharedStacks.toast.startFailed'), e)
239
+ present(e, 'settings.sharedStacks.toast.startFailed')
250
240
  } finally {
251
241
  busyId.value = null
252
242
  }
@@ -257,7 +247,7 @@ async function stop(stack: SharedStack) {
257
247
  try {
258
248
  await store.teardown(stack.id)
259
249
  } catch (e) {
260
- notifyError(t('settings.sharedStacks.toast.stopFailed'), e)
250
+ present(e, 'settings.sharedStacks.toast.stopFailed')
261
251
  } finally {
262
252
  busyId.value = null
263
253
  }
@@ -271,7 +261,7 @@ async function remove(stack: SharedStack) {
271
261
  await store.remove(stack.id)
272
262
  toastDone('remove', noun)
273
263
  } catch (e) {
274
- notifyError(t('settings.sharedStacks.toast.removeFailed'), e)
264
+ present(e, 'settings.sharedStacks.toast.removeFailed')
275
265
  } finally {
276
266
  busyId.value = null
277
267
  }
@@ -16,7 +16,7 @@ import type { TaskTypeSuppression } from '~/types/domain'
16
16
  const { t } = useI18n()
17
17
  const api = useApi()
18
18
  const workspace = useWorkspaceStore()
19
- const toast = useToast()
19
+ const { present } = usePipelineErrorToast()
20
20
 
21
21
  const rows = ref<TaskTypeSuppression[]>([])
22
22
  const loading = ref(false)
@@ -31,7 +31,7 @@ async function load() {
31
31
  try {
32
32
  rows.value = (await api.listTaskTypeSuppressions(workspace.requireId())).taskTypes
33
33
  } catch (e) {
34
- fail(e)
34
+ present(e, 'settings.taskTypeSuppressions.saveFailed')
35
35
  } finally {
36
36
  loading.value = false
37
37
  }
@@ -52,20 +52,11 @@ async function toggle(row: TaskTypeSuppression, offered: boolean) {
52
52
  rows.value = result.taskTypes
53
53
  await workspace.refresh()
54
54
  } catch (e) {
55
- fail(e)
55
+ present(e, 'settings.taskTypeSuppressions.saveFailed')
56
56
  } finally {
57
57
  busyId.value = null
58
58
  }
59
59
  }
60
-
61
- function fail(e: unknown) {
62
- toast.add({
63
- title: t('settings.taskTypeSuppressions.saveFailed'),
64
- description: e instanceof Error ? e.message : String(e),
65
- icon: 'i-lucide-triangle-alert',
66
- color: 'error',
67
- })
68
- }
69
60
  </script>
70
61
 
71
62
  <template>
@@ -15,6 +15,7 @@ const { t } = useI18n()
15
15
  const ui = useUiStore()
16
16
  const store = useUserSecretsStore()
17
17
  const toast = useToast()
18
+ const { present } = usePipelineErrorToast()
18
19
  const { confirm } = useConfirm()
19
20
 
20
21
  const open = computed({
@@ -75,15 +76,6 @@ function buildPayload(): { secret: string; metadata?: Record<string, string> } |
75
76
  return { secret, ...(Object.keys(metadata).length ? { metadata } : {}) }
76
77
  }
77
78
 
78
- function notifyError(title: string, e: unknown) {
79
- toast.add({
80
- title,
81
- description: e instanceof Error ? e.message : String(e),
82
- icon: 'i-lucide-triangle-alert',
83
- color: 'error',
84
- })
85
- }
86
-
87
79
  async function test() {
88
80
  const payload = buildPayload()
89
81
  if (!payload) return
@@ -120,7 +112,7 @@ async function save() {
120
112
  color: 'success',
121
113
  })
122
114
  } catch (e) {
123
- notifyError(t('settings.userSecrets.toast.saveFailed'), e)
115
+ present(e, 'settings.userSecrets.toast.saveFailed')
124
116
  } finally {
125
117
  busy.value = false
126
118
  }
@@ -143,7 +135,7 @@ async function remove() {
143
135
  resetDraft()
144
136
  toast.add({ title: t('settings.userSecrets.toast.removed'), icon: 'i-lucide-check' })
145
137
  } catch (e) {
146
- notifyError(t('settings.userSecrets.toast.removeFailed'), e)
138
+ present(e, 'settings.userSecrets.toast.removeFailed')
147
139
  } finally {
148
140
  busy.value = false
149
141
  }
@@ -22,6 +22,7 @@ const { t } = useI18n()
22
22
  const slots = useReactiveSlots<AppSlots>()
23
23
  const store = useWorkspaceSettingsStore()
24
24
  const toast = useToast()
25
+ const { present } = usePipelineErrorToast()
25
26
 
26
27
  /**
27
28
  * The fields to render. A malformed key is dropped (the store would refuse every save) and
@@ -71,12 +72,7 @@ async function save() {
71
72
  color: 'success',
72
73
  })
73
74
  } catch (e) {
74
- toast.add({
75
- title: t('settings.workspaceSettings.toast.saveFailed'),
76
- description: e instanceof Error ? e.message : String(e),
77
- icon: 'i-lucide-triangle-alert',
78
- color: 'error',
79
- })
75
+ present(e, 'settings.workspaceSettings.toast.saveFailed')
80
76
  } finally {
81
77
  saving.value = false
82
78
  }
@@ -29,6 +29,7 @@ const store = useWorkspaceSettingsStore()
29
29
  const workspace = useWorkspaceStore()
30
30
  const access = useWorkspaceAccess()
31
31
  const toast = useToast()
32
+ const { present } = usePipelineErrorToast()
32
33
  const slots = useReactiveSlots<AppSlots>()
33
34
  // Whether the deployment registers any reusable operation at all, hidden or not, so the
34
35
  // Operations tab exists only where there is something for it to manage. Not the OFFERED catalog:
@@ -289,12 +290,7 @@ async function save() {
289
290
  color: 'success',
290
291
  })
291
292
  } catch (e) {
292
- toast.add({
293
- title: t('settings.workspaceSettings.toast.saveFailed'),
294
- description: e instanceof Error ? e.message : String(e),
295
- icon: 'i-lucide-triangle-alert',
296
- color: 'error',
297
- })
293
+ present(e, 'settings.workspaceSettings.toast.saveFailed')
298
294
  } finally {
299
295
  saving.value = false
300
296
  }