@cat-factory/app 0.261.3 → 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.
- package/README.md +40 -0
- package/app/components/board/AddTaskModal.vue +10 -6
- package/app/components/board/AgentStopButton.vue +2 -5
- package/app/components/board/CreateInitiativeModal.vue +2 -7
- package/app/components/board/RecurringPipelineModal.vue +10 -6
- package/app/components/bootstrap/BootstrapModal.vue +5 -24
- package/app/components/brainstorm/BrainstormWindow.vue +7 -15
- package/app/components/clarity/ClarityReviewWindow.vue +7 -15
- package/app/components/documents/DocumentImportModal.vue +2 -6
- package/app/components/documents/DocumentSourceConnectModal.vue +3 -12
- package/app/components/documents/DocumentSyncState.vue +2 -7
- package/app/components/documents/DocumentTemplatesModal.vue +4 -19
- package/app/components/documents/SpawnPreviewModal.vue +3 -12
- package/app/components/foundational/FoundationalServiceCatalogList.vue +3 -11
- package/app/components/foundational/FoundationalServiceRegistry.vue +3 -11
- package/app/components/foundational/FoundationalServiceSources.vue +5 -13
- package/app/components/foundational/FoundationalSuppressions.vue +2 -6
- package/app/components/fragments/FragmentLibraryManager.vue +12 -20
- package/app/components/github/AddServiceFromRepoModal.vue +3 -12
- package/app/components/github/GitHubConnect.vue +4 -12
- package/app/components/github/GitHubPanel.vue +9 -17
- package/app/components/github/RepoTreeBrowser.vue +2 -7
- package/app/components/humanTest/HumanTestWindow.vue +2 -7
- package/app/components/initiative/InitiativeTrackerWindow.vue +2 -3
- package/app/components/layout/AccountAuditLog.vue +2 -7
- package/app/components/layout/AccountDeploymentSettings.vue +11 -51
- package/app/components/layout/AccountModelPolicySettings.vue +2 -5
- package/app/components/layout/AccountPlatformAlertSettings.vue +2 -5
- package/app/components/layout/AccountRunCredentialSettings.vue +2 -5
- package/app/components/layout/AccountTeamSettings.vue +9 -18
- package/app/components/layout/BoardSwitcher.vue +6 -14
- package/app/components/layout/BoardToolbar.vue +3 -10
- package/app/components/layout/NotificationsInbox.vue +3 -13
- package/app/components/layout/WorkspaceMembersSettings.vue +6 -15
- package/app/components/notifications/NotificationSettingsPanel.vue +3 -11
- package/app/components/panels/DecisionModal.vue +2 -7
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +4 -20
- package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +3 -11
- package/app/components/panels/inspector/ServiceTestSecrets.vue +3 -11
- package/app/components/panels/inspector/ServiceValidationConfig.vue +4 -12
- package/app/components/pipeline/PipelineBuilder.vue +4 -7
- package/app/components/providers/ApiKeysSection.vue +4 -15
- package/app/components/providers/PersonalCredentialModal.vue +2 -5
- package/app/components/providers/PersonalSubscriptionSection.vue +3 -10
- package/app/components/providers/VendorCredentialsModal.vue +5 -20
- package/app/components/requirements/RequirementsReviewWindow.vue +11 -19
- package/app/components/sandbox/SandboxPanel.vue +4 -18
- package/app/components/settings/ApiTokensPanel.vue +4 -12
- package/app/components/settings/BudgetSettings.vue +2 -5
- package/app/components/settings/CloudflareHandlerSection.vue +3 -12
- package/app/components/settings/ConsensusGroupsSection.vue +3 -12
- package/app/components/settings/CustomManifestTypeEditor.vue +3 -13
- package/app/components/settings/DefaultProvisionTypeSection.vue +2 -5
- package/app/components/settings/InfraHandlersConfigurator.vue +7 -14
- package/app/components/settings/InfrastructureBackendPicker.vue +2 -7
- package/app/components/settings/IssueTrackerPanel.vue +4 -18
- package/app/components/settings/LocalContainerPoolSettings.vue +2 -6
- package/app/components/settings/LocalModelEndpointsPanel.vue +3 -11
- package/app/components/settings/ModelConfigurationPanel.vue +13 -16
- package/app/components/settings/ModelPresetHealthModal.vue +3 -13
- package/app/components/settings/ObservabilityConnectionPanel.vue +6 -14
- package/app/components/settings/OpenRouterCatalogPanel.vue +3 -12
- package/app/components/settings/PackageRegistriesPanel.vue +4 -12
- package/app/components/settings/ProviderConnectionTab.vue +5 -13
- package/app/components/settings/RiskPolicyHealthModal.vue +2 -7
- package/app/components/settings/RiskPolicyPanel.vue +5 -13
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +2 -7
- package/app/components/settings/SharedStacksPanel.vue +10 -20
- package/app/components/settings/TaskTypeSuppressionsPanel.vue +3 -12
- package/app/components/settings/UserSecretsSection.vue +3 -11
- package/app/components/settings/WorkspaceMetadataSettings.vue +2 -6
- package/app/components/settings/WorkspaceSettingsPanel.vue +2 -6
- package/app/components/skills/SkillLibraryManager.vue +5 -13
- package/app/components/slack/SlackPanel.vue +7 -15
- package/app/components/tasks/BugHuntModal.vue +2 -6
- package/app/components/tasks/TaskImportModal.vue +2 -6
- package/app/components/tasks/TaskSourceConnectModal.vue +4 -18
- package/app/composables/useBlockDeletion.ts +2 -6
- package/app/composables/useCopyToClipboard.ts +7 -1
- package/app/composables/usePipelineErrorToast.spec.ts +76 -16
- package/app/composables/usePipelineErrorToast.ts +109 -50
- package/app/stores/board/context.ts +9 -2
- package/app/stores/board/dependencies.ts +3 -13
- package/app/stores/board/mutations.ts +2 -7
- package/app/stores/board/placement.ts +12 -25
- package/app/stores/board/removal.ts +2 -7
- package/app/stores/board.spec.ts +15 -8
- package/app/stores/board.ts +11 -1
- package/app/stores/recurringPipelines.ts +5 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -195,6 +195,46 @@ The failure is silent, which is why this is a rule rather than a preference. An
|
|
|
195
195
|
|
|
196
196
|
`scripts/check-component-imports.mjs` enforces it (CI's `repo-guards` job). If a panel section is missing and the data looks right, check the import first.
|
|
197
197
|
|
|
198
|
+
### Every failure toast goes through ONE funnel
|
|
199
|
+
|
|
200
|
+
**A failed call is reported with `usePipelineErrorToast().present(error, titleKey)`.** Never build
|
|
201
|
+
`toast.add({ title, description: e instanceof Error ? e.message : String(e) })`, and never wrap that
|
|
202
|
+
shape in a per-component `notifyError(title, e)` helper (29 components had a copy of the same six
|
|
203
|
+
lines, plus four more spellings of it).
|
|
204
|
+
|
|
205
|
+
The funnel is not a formatting convenience. Four properties are what it exists for, and a hand-built
|
|
206
|
+
toast has none of them:
|
|
207
|
+
|
|
208
|
+
- **Translated copy.** The description is resolved from the envelope's status class or its
|
|
209
|
+
`details.reason`; the backend's untranslated prose is DETAIL, never the headline (see the i18n
|
|
210
|
+
section). A hand-built toast shows English to every locale.
|
|
211
|
+
- **It does not auto-dismiss.** An error is the one toast a reader has to finish, quote, or act on,
|
|
212
|
+
and a ~5s dismissal took it away mid-sentence. It keeps its close button, so leaving is a choice.
|
|
213
|
+
- **One-click copy of the whole thing**, through `useCopyToClipboard` (so the copy's own
|
|
214
|
+
success/failure is reported rather than silently no-op'ing in an insecure context). Selecting text
|
|
215
|
+
in a toast is fiddly and impossible once it is gone.
|
|
216
|
+
- **The `requestId` travels with it.** `mountRequestLogging` puts that id on every error envelope,
|
|
217
|
+
and it is the ONLY join between what the user saw and the one server log line that explains it. A
|
|
218
|
+
report that arrives without it costs whoever reads it the entire diagnosis.
|
|
219
|
+
|
|
220
|
+
`present` takes a KEY, not a resolved title (plus optional interpolation params), so it can resolve
|
|
221
|
+
its own copy. A store passes it through its context alongside `api`/`toast` (see
|
|
222
|
+
`stores/board/context.ts`) rather than calling the composable per write. A site with BESPOKE copy for
|
|
223
|
+
a recognised refusal keeps that branch and drains only its fallback into the funnel
|
|
224
|
+
(`stores/board/placement.ts`, `components/board/AddTaskModal.vue`).
|
|
225
|
+
|
|
226
|
+
**A FAILED CALL, though, not every refusal.** The funnel's whole job is to classify what the backend
|
|
227
|
+
answered, so a local check that never left the browser must not be dressed up as one: a synthesized
|
|
228
|
+
`new Error(t('...'))` has no envelope and no status, which is precisely the input `describeGenericFailure`
|
|
229
|
+
reads as a network fault. A blank required field then renders as "The server could not be reached",
|
|
230
|
+
with the real sentence hidden behind a disclosure. Client-side validation stays a plain
|
|
231
|
+
`toast.add` with translated title and description (`components/settings/ModelConfigurationPanel.vue`).
|
|
232
|
+
|
|
233
|
+
The still-open remainder is the INLINE family: `error.value = e.message` rendered in a panel, and
|
|
234
|
+
`testResult = { ok: false, message }` rendered by `ConnectionTestVerdict`. Those need a render
|
|
235
|
+
surface rather than a toast, and are tracked as G4 in
|
|
236
|
+
[`error-message-coverage.md`](https://github.com/kibertoad/cat-factory/blob/main/docs/initiatives/error-message-coverage.md).
|
|
237
|
+
|
|
198
238
|
### Type a chip map with `BadgeColor`, never `string`
|
|
199
239
|
|
|
200
240
|
A status → chip map feeding a `<UBadge :color="…">` types its values as `BadgeColor` (`utils/badge.ts`), which is derived from `UBadge`'s own prop type rather than restated as a literal union. Typed `string`, the binding does not compile and the reflex is `as any` at each call site: seven of them had accumulated. That cast also accepts a colour Nuxt UI does not define, which renders as an unstyled badge with nothing failing.
|
|
@@ -59,6 +59,7 @@ const pipelines = usePipelinesStore()
|
|
|
59
59
|
const agentConfig = useAgentConfigStore()
|
|
60
60
|
const fragments = useFragmentsStore()
|
|
61
61
|
const toast = useToast()
|
|
62
|
+
const { present } = usePipelineErrorToast()
|
|
62
63
|
const { t } = useI18n()
|
|
63
64
|
|
|
64
65
|
const { resolvePending, linkPending, presentLinkFailures } = useContextLinking()
|
|
@@ -730,12 +731,15 @@ async function submitCreate(acknowledgeReviewDebt: boolean) {
|
|
|
730
731
|
openReviewFrictionDialog(conflict)
|
|
731
732
|
return
|
|
732
733
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
734
|
+
const refusal = createRefusalMessage(e)
|
|
735
|
+
if (refusal) {
|
|
736
|
+
toast.add({
|
|
737
|
+
title: t('board.addTask.addFailedTitle'),
|
|
738
|
+
description: refusal,
|
|
739
|
+
icon: 'i-lucide-triangle-alert',
|
|
740
|
+
color: 'error',
|
|
741
|
+
})
|
|
742
|
+
} else present(e, 'board.addTask.addFailedTitle')
|
|
739
743
|
} finally {
|
|
740
744
|
saving.value = false
|
|
741
745
|
}
|
|
@@ -23,6 +23,7 @@ const { t } = useI18n()
|
|
|
23
23
|
const agentRuns = useAgentRunsStore()
|
|
24
24
|
const access = useWorkspaceAccess()
|
|
25
25
|
const toast = useToast()
|
|
26
|
+
const { present } = usePipelineErrorToast()
|
|
26
27
|
const { confirm } = useConfirm()
|
|
27
28
|
const stopping = ref(false)
|
|
28
29
|
|
|
@@ -50,11 +51,7 @@ async function stop() {
|
|
|
50
51
|
color: 'warning',
|
|
51
52
|
})
|
|
52
53
|
} catch (e) {
|
|
53
|
-
|
|
54
|
-
title: t('board.stop.stopFailed'),
|
|
55
|
-
description: e instanceof Error ? e.message : String(e),
|
|
56
|
-
color: 'error',
|
|
57
|
-
})
|
|
54
|
+
present(e, 'board.stop.stopFailed')
|
|
58
55
|
} finally {
|
|
59
56
|
stopping.value = false
|
|
60
57
|
}
|
|
@@ -30,7 +30,7 @@ import type { PendingContext } from '~/composables/useContextLinking'
|
|
|
30
30
|
const ui = useUiStore()
|
|
31
31
|
const board = useBoardStore()
|
|
32
32
|
const initiatives = useInitiativesStore()
|
|
33
|
-
const
|
|
33
|
+
const { present } = usePipelineErrorToast()
|
|
34
34
|
const { t } = useI18n()
|
|
35
35
|
const { resolvePending, linkPending, presentLinkFailures } = useContextLinking()
|
|
36
36
|
|
|
@@ -170,12 +170,7 @@ async function create() {
|
|
|
170
170
|
// Select the fresh block so the inspector offers "Run planning" right away.
|
|
171
171
|
ui.select(block.id)
|
|
172
172
|
} catch (e) {
|
|
173
|
-
|
|
174
|
-
title: t('initiative.create.failedTitle'),
|
|
175
|
-
description: e instanceof Error ? e.message : String(e),
|
|
176
|
-
icon: 'i-lucide-triangle-alert',
|
|
177
|
-
color: 'error',
|
|
178
|
-
})
|
|
173
|
+
present(e, 'initiative.create.failedTitle')
|
|
179
174
|
} finally {
|
|
180
175
|
submitting.value = false
|
|
181
176
|
}
|
|
@@ -20,6 +20,7 @@ const recurring = useRecurringPipelinesStore()
|
|
|
20
20
|
const tracker = useTrackerStore()
|
|
21
21
|
const tasks = useTasksStore()
|
|
22
22
|
const toast = useToast()
|
|
23
|
+
const { present } = usePipelineErrorToast()
|
|
23
24
|
const access = useWorkspaceAccess()
|
|
24
25
|
const { t, te } = useI18n()
|
|
25
26
|
|
|
@@ -375,12 +376,15 @@ async function add() {
|
|
|
375
376
|
})
|
|
376
377
|
ui.closeAddRecurring()
|
|
377
378
|
} catch (e) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
379
|
+
const refusal = intakeRefusalCopy(e)
|
|
380
|
+
if (refusal) {
|
|
381
|
+
toast.add({
|
|
382
|
+
title: t('board.recurring.addFailedTitle'),
|
|
383
|
+
description: refusal,
|
|
384
|
+
icon: 'i-lucide-triangle-alert',
|
|
385
|
+
color: 'error',
|
|
386
|
+
})
|
|
387
|
+
} else present(e, 'board.recurring.addFailedTitle')
|
|
384
388
|
} finally {
|
|
385
389
|
saving.value = false
|
|
386
390
|
}
|
|
@@ -14,6 +14,7 @@ const agentRuns = useAgentRunsStore()
|
|
|
14
14
|
const github = useGitHubStore()
|
|
15
15
|
const board = useBoardStore()
|
|
16
16
|
const toast = useToast()
|
|
17
|
+
const { present } = usePipelineErrorToast()
|
|
17
18
|
const { freeFramePosition, focusFrame } = useFramePlacement()
|
|
18
19
|
const { t } = useI18n()
|
|
19
20
|
const { confirmAction, toastDone } = useConfirmAction()
|
|
@@ -205,12 +206,7 @@ async function openCreateRepo() {
|
|
|
205
206
|
color: 'success',
|
|
206
207
|
})
|
|
207
208
|
} catch (e) {
|
|
208
|
-
toast.
|
|
209
|
-
title: t('bootstrap.toast.repoCreateFailed'),
|
|
210
|
-
description: e instanceof Error ? e.message : String(e),
|
|
211
|
-
icon: 'i-lucide-triangle-alert',
|
|
212
|
-
color: 'error',
|
|
213
|
-
})
|
|
209
|
+
present(e, 'bootstrap.toast.repoCreateFailed')
|
|
214
210
|
} finally {
|
|
215
211
|
creatingRepo.value = false
|
|
216
212
|
}
|
|
@@ -291,12 +287,7 @@ async function launch() {
|
|
|
291
287
|
ui.closeBootstrap()
|
|
292
288
|
}
|
|
293
289
|
} catch (e) {
|
|
294
|
-
toast.
|
|
295
|
-
title: t('bootstrap.toast.bootstrapFailed'),
|
|
296
|
-
description: e instanceof Error ? e.message : String(e),
|
|
297
|
-
icon: 'i-lucide-triangle-alert',
|
|
298
|
-
color: 'error',
|
|
299
|
-
})
|
|
290
|
+
present(e, 'bootstrap.toast.bootstrapFailed')
|
|
300
291
|
} finally {
|
|
301
292
|
launching.value = false
|
|
302
293
|
}
|
|
@@ -382,12 +373,7 @@ async function saveArch() {
|
|
|
382
373
|
archForm.value = blankForm()
|
|
383
374
|
archRepoSlug.value = undefined
|
|
384
375
|
} catch (e) {
|
|
385
|
-
toast.
|
|
386
|
-
title: t('bootstrap.toast.saveArchFailed'),
|
|
387
|
-
description: e instanceof Error ? e.message : String(e),
|
|
388
|
-
icon: 'i-lucide-triangle-alert',
|
|
389
|
-
color: 'error',
|
|
390
|
-
})
|
|
376
|
+
present(e, 'bootstrap.toast.saveArchFailed')
|
|
391
377
|
} finally {
|
|
392
378
|
savingArch.value = false
|
|
393
379
|
}
|
|
@@ -400,12 +386,7 @@ async function removeArch(a: ReferenceArchitecture) {
|
|
|
400
386
|
if (selectedArchId.value === a.id) selectedArchId.value = undefined
|
|
401
387
|
toastDone('remove', a.name)
|
|
402
388
|
} catch (e) {
|
|
403
|
-
toast.
|
|
404
|
-
title: t('bootstrap.toast.deleteFailed'),
|
|
405
|
-
description: e instanceof Error ? e.message : String(e),
|
|
406
|
-
icon: 'i-lucide-triangle-alert',
|
|
407
|
-
color: 'error',
|
|
408
|
-
})
|
|
389
|
+
present(e, 'bootstrap.toast.deleteFailed')
|
|
409
390
|
}
|
|
410
391
|
}
|
|
411
392
|
|
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
const board = useBoardStore()
|
|
24
24
|
const brainstorm = useBrainstormStore()
|
|
25
25
|
const toast = useToast()
|
|
26
|
+
const { present } = usePipelineErrorToast()
|
|
26
27
|
const { t } = useI18n()
|
|
27
28
|
const access = useWorkspaceAccess()
|
|
28
29
|
|
|
@@ -143,15 +144,6 @@ const STATUS_LABELS: Record<ReviewItemStatus, string> = {
|
|
|
143
144
|
recommend_requested: 'brainstorm.itemStatus.recommend_requested',
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
function notifyError(title: string, e: unknown) {
|
|
147
|
-
toast.add({
|
|
148
|
-
title,
|
|
149
|
-
description: e instanceof Error ? e.message : String(e),
|
|
150
|
-
icon: 'i-lucide-triangle-alert',
|
|
151
|
-
color: 'error',
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
|
|
155
147
|
async function submitReply(item: BrainstormItem) {
|
|
156
148
|
if (!session.value) return
|
|
157
149
|
const text = (drafts.value[item.id] ?? '').trim()
|
|
@@ -160,7 +152,7 @@ async function submitReply(item: BrainstormItem) {
|
|
|
160
152
|
await brainstorm.reply(session.value, item.id, text)
|
|
161
153
|
drafts.value = { ...drafts.value, [item.id]: '' }
|
|
162
154
|
} catch (e) {
|
|
163
|
-
|
|
155
|
+
present(e, 'brainstorm.toast.saveChoiceError')
|
|
164
156
|
}
|
|
165
157
|
}
|
|
166
158
|
|
|
@@ -169,7 +161,7 @@ async function setStatus(item: BrainstormItem, itemStatus: BrainstormItemStatus)
|
|
|
169
161
|
try {
|
|
170
162
|
await brainstorm.setItemStatus(session.value, item.id, itemStatus)
|
|
171
163
|
} catch (e) {
|
|
172
|
-
|
|
164
|
+
present(e, 'brainstorm.toast.updateOptionError')
|
|
173
165
|
}
|
|
174
166
|
}
|
|
175
167
|
|
|
@@ -178,7 +170,7 @@ async function incorporate(feedback?: string) {
|
|
|
178
170
|
try {
|
|
179
171
|
await brainstorm.incorporate(session.value, feedback)
|
|
180
172
|
} catch (e) {
|
|
181
|
-
|
|
173
|
+
present(e, 'brainstorm.toast.incorporateError')
|
|
182
174
|
return
|
|
183
175
|
}
|
|
184
176
|
redoComment.value = ''
|
|
@@ -206,7 +198,7 @@ async function reReview() {
|
|
|
206
198
|
icon: 'i-lucide-sparkles',
|
|
207
199
|
})
|
|
208
200
|
} catch (e) {
|
|
209
|
-
|
|
201
|
+
present(e, 'brainstorm.toast.reReviewError')
|
|
210
202
|
}
|
|
211
203
|
}
|
|
212
204
|
|
|
@@ -217,7 +209,7 @@ async function proceed() {
|
|
|
217
209
|
await brainstorm.proceed(blockId.value, activeStage.value)
|
|
218
210
|
toast.add({ title: t('brainstorm.toast.proceeding'), icon: 'i-lucide-arrow-right' })
|
|
219
211
|
} catch (e) {
|
|
220
|
-
|
|
212
|
+
present(e, 'brainstorm.toast.proceedError')
|
|
221
213
|
} finally {
|
|
222
214
|
acting.value = false
|
|
223
215
|
}
|
|
@@ -237,7 +229,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
237
229
|
toast.add({ title: t('brainstorm.toast.extraRoundGranted'), icon: 'i-lucide-rotate-cw' })
|
|
238
230
|
}
|
|
239
231
|
} catch (e) {
|
|
240
|
-
|
|
232
|
+
present(e, 'brainstorm.toast.resolveError')
|
|
241
233
|
} finally {
|
|
242
234
|
acting.value = false
|
|
243
235
|
}
|
|
@@ -24,6 +24,7 @@ const board = useBoardStore()
|
|
|
24
24
|
const clarity = useClarityStore()
|
|
25
25
|
const models = useModelsStore()
|
|
26
26
|
const toast = useToast()
|
|
27
|
+
const { present } = usePipelineErrorToast()
|
|
27
28
|
const { t } = useI18n()
|
|
28
29
|
const access = useWorkspaceAccess()
|
|
29
30
|
|
|
@@ -145,15 +146,6 @@ const STATUS_LABELS: Record<ReviewItemStatus, string> = {
|
|
|
145
146
|
recommend_requested: 'clarity.itemStatus.recommend_requested',
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
function notifyError(title: string, e: unknown) {
|
|
149
|
-
toast.add({
|
|
150
|
-
title,
|
|
151
|
-
description: e instanceof Error ? e.message : String(e),
|
|
152
|
-
icon: 'i-lucide-triangle-alert',
|
|
153
|
-
color: 'error',
|
|
154
|
-
})
|
|
155
|
-
}
|
|
156
|
-
|
|
157
149
|
// Answers auto-save on blur — no explicit "save" button (matching the requirements window, so
|
|
158
150
|
// muscle memory carries across the two, UX-34). The textarea is pre-seeded with the recorded
|
|
159
151
|
// reply (see the watch below); persist only when the trimmed draft actually differs from what's
|
|
@@ -165,7 +157,7 @@ async function persistDraft(item: ClarityReviewItem, r: ClarityReview | null = r
|
|
|
165
157
|
try {
|
|
166
158
|
await clarity.reply(r, item.id, text)
|
|
167
159
|
} catch (e) {
|
|
168
|
-
|
|
160
|
+
present(e, 'clarity.error.saveAnswer')
|
|
169
161
|
}
|
|
170
162
|
}
|
|
171
163
|
|
|
@@ -223,7 +215,7 @@ async function setStatus(item: ClarityReviewItem, itemStatus: ClarityItemStatus)
|
|
|
223
215
|
try {
|
|
224
216
|
await clarity.setItemStatus(review.value, item.id, itemStatus)
|
|
225
217
|
} catch (e) {
|
|
226
|
-
|
|
218
|
+
present(e, 'clarity.error.updateFinding')
|
|
227
219
|
}
|
|
228
220
|
}
|
|
229
221
|
|
|
@@ -233,7 +225,7 @@ async function incorporate(feedback?: string) {
|
|
|
233
225
|
await flushDrafts()
|
|
234
226
|
await clarity.incorporate(review.value, feedback)
|
|
235
227
|
} catch (e) {
|
|
236
|
-
|
|
228
|
+
present(e, 'clarity.error.incorporate')
|
|
237
229
|
return
|
|
238
230
|
}
|
|
239
231
|
redoComment.value = ''
|
|
@@ -263,7 +255,7 @@ async function reReview() {
|
|
|
263
255
|
icon: 'i-lucide-sparkles',
|
|
264
256
|
})
|
|
265
257
|
} catch (e) {
|
|
266
|
-
|
|
258
|
+
present(e, 'clarity.error.reReview')
|
|
267
259
|
}
|
|
268
260
|
}
|
|
269
261
|
|
|
@@ -275,7 +267,7 @@ async function proceed() {
|
|
|
275
267
|
await clarity.proceed(blockId.value)
|
|
276
268
|
toast.add({ title: t('clarity.toast.proceeding'), icon: 'i-lucide-arrow-right' })
|
|
277
269
|
} catch (e) {
|
|
278
|
-
|
|
270
|
+
present(e, 'clarity.error.proceed')
|
|
279
271
|
} finally {
|
|
280
272
|
acting.value = false
|
|
281
273
|
}
|
|
@@ -295,7 +287,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
295
287
|
toast.add({ title: t('clarity.toast.extraRoundGranted'), icon: 'i-lucide-rotate-cw' })
|
|
296
288
|
}
|
|
297
289
|
} catch (e) {
|
|
298
|
-
|
|
290
|
+
present(e, 'clarity.error.resolve')
|
|
299
291
|
} finally {
|
|
300
292
|
acting.value = false
|
|
301
293
|
}
|
|
@@ -11,6 +11,7 @@ const { t } = useI18n()
|
|
|
11
11
|
const ui = useUiStore()
|
|
12
12
|
const documents = useDocumentsStore()
|
|
13
13
|
const toast = useToast()
|
|
14
|
+
const { present } = usePipelineErrorToast()
|
|
14
15
|
|
|
15
16
|
const open = computed({
|
|
16
17
|
get: () => ui.documentImport !== null,
|
|
@@ -61,12 +62,7 @@ async function doImport() {
|
|
|
61
62
|
icon: 'i-lucide-file-down',
|
|
62
63
|
})
|
|
63
64
|
} catch (e) {
|
|
64
|
-
|
|
65
|
-
title: t('documents.import.importFailed'),
|
|
66
|
-
description: e instanceof Error ? e.message : String(e),
|
|
67
|
-
icon: 'i-lucide-triangle-alert',
|
|
68
|
-
color: 'error',
|
|
69
|
-
})
|
|
65
|
+
present(e, 'documents.import.importFailed')
|
|
70
66
|
} finally {
|
|
71
67
|
importing.value = false
|
|
72
68
|
}
|
|
@@ -18,6 +18,7 @@ const { t } = useI18n()
|
|
|
18
18
|
const ui = useUiStore()
|
|
19
19
|
const documents = useDocumentsStore()
|
|
20
20
|
const toast = useToast()
|
|
21
|
+
const { present } = usePipelineErrorToast()
|
|
21
22
|
const { confirmAction } = useConfirmAction()
|
|
22
23
|
|
|
23
24
|
const source = computed(() => ui.documentConnect?.source ?? null)
|
|
@@ -57,12 +58,7 @@ async function connectWithOAuth() {
|
|
|
57
58
|
// path (an app un-registered between the probe and the click).
|
|
58
59
|
await documents.beginOAuthConnect(source.value)
|
|
59
60
|
} catch (e) {
|
|
60
|
-
|
|
61
|
-
title: t('documents.connect.oauth.failed', { source: descriptor.value?.label ?? '' }),
|
|
62
|
-
description: e instanceof Error ? e.message : String(e),
|
|
63
|
-
icon: 'i-lucide-triangle-alert',
|
|
64
|
-
color: 'error',
|
|
65
|
-
})
|
|
61
|
+
present(e, 'documents.connect.oauth.failed', { source: descriptor.value?.label ?? '' })
|
|
66
62
|
} finally {
|
|
67
63
|
startingOAuth.value = false
|
|
68
64
|
}
|
|
@@ -93,12 +89,7 @@ async function submit() {
|
|
|
93
89
|
})
|
|
94
90
|
ui.closeDocumentConnect()
|
|
95
91
|
} catch (e) {
|
|
96
|
-
|
|
97
|
-
title: t('documents.connect.connectFailed'),
|
|
98
|
-
description: e instanceof Error ? e.message : String(e),
|
|
99
|
-
icon: 'i-lucide-triangle-alert',
|
|
100
|
-
color: 'error',
|
|
101
|
-
})
|
|
92
|
+
present(e, 'documents.connect.connectFailed')
|
|
102
93
|
} finally {
|
|
103
94
|
saving.value = false
|
|
104
95
|
}
|
|
@@ -39,7 +39,7 @@ const props = defineProps<{ doc: SourceDocument }>()
|
|
|
39
39
|
|
|
40
40
|
const { t, d } = useI18n()
|
|
41
41
|
const documents = useDocumentsStore()
|
|
42
|
-
const
|
|
42
|
+
const { present } = usePipelineErrorToast()
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* The source to ask, or null for an origin with nobody to ask. An `upload` was handed to the
|
|
@@ -159,12 +159,7 @@ async function refresh() {
|
|
|
159
159
|
try {
|
|
160
160
|
await documents.refresh(source, props.doc.externalId)
|
|
161
161
|
} catch (e) {
|
|
162
|
-
|
|
163
|
-
title: t('documents.freshness.refreshFailed'),
|
|
164
|
-
description: e instanceof Error ? e.message : String(e),
|
|
165
|
-
icon: 'i-lucide-triangle-alert',
|
|
166
|
-
color: 'error',
|
|
167
|
-
})
|
|
162
|
+
present(e, 'documents.freshness.refreshFailed')
|
|
168
163
|
}
|
|
169
164
|
}
|
|
170
165
|
</script>
|
|
@@ -11,7 +11,7 @@ import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
|
11
11
|
const { t } = useI18n()
|
|
12
12
|
const ui = useUiStore()
|
|
13
13
|
const documents = useDocumentsStore()
|
|
14
|
-
const
|
|
14
|
+
const { present } = usePipelineErrorToast()
|
|
15
15
|
|
|
16
16
|
const open = computed({
|
|
17
17
|
get: () => ui.documentTemplates,
|
|
@@ -34,12 +34,7 @@ watch(
|
|
|
34
34
|
// Surface a load failure instead of silently rendering an empty panel (which would invite
|
|
35
35
|
// re-linking over links that still exist server-side).
|
|
36
36
|
Promise.all([documents.loadDocuments(), documents.loadRoleLinks()]).catch((e) => {
|
|
37
|
-
|
|
38
|
-
title: t('documents.templates.loadFailed'),
|
|
39
|
-
description: e instanceof Error ? e.message : String(e),
|
|
40
|
-
icon: 'i-lucide-triangle-alert',
|
|
41
|
-
color: 'error',
|
|
42
|
-
})
|
|
37
|
+
present(e, 'documents.templates.loadFailed')
|
|
43
38
|
})
|
|
44
39
|
}
|
|
45
40
|
},
|
|
@@ -73,12 +68,7 @@ async function link(role: DocumentLinkRole) {
|
|
|
73
68
|
await documents.linkForKind(doc.source, doc.externalId, role, kind.value)
|
|
74
69
|
pick.value = undefined
|
|
75
70
|
} catch (e) {
|
|
76
|
-
|
|
77
|
-
title: t('documents.templates.linkFailed'),
|
|
78
|
-
description: e instanceof Error ? e.message : String(e),
|
|
79
|
-
icon: 'i-lucide-triangle-alert',
|
|
80
|
-
color: 'error',
|
|
81
|
-
})
|
|
71
|
+
present(e, 'documents.templates.linkFailed')
|
|
82
72
|
} finally {
|
|
83
73
|
busy.value = false
|
|
84
74
|
}
|
|
@@ -89,12 +79,7 @@ async function unlink(doc: SourceDocument) {
|
|
|
89
79
|
try {
|
|
90
80
|
await documents.unlinkForKind(doc.source, doc.externalId)
|
|
91
81
|
} catch (e) {
|
|
92
|
-
|
|
93
|
-
title: t('documents.templates.linkFailed'),
|
|
94
|
-
description: e instanceof Error ? e.message : String(e),
|
|
95
|
-
icon: 'i-lucide-triangle-alert',
|
|
96
|
-
color: 'error',
|
|
97
|
-
})
|
|
82
|
+
present(e, 'documents.templates.linkFailed')
|
|
98
83
|
} finally {
|
|
99
84
|
busy.value = false
|
|
100
85
|
}
|
|
@@ -17,6 +17,7 @@ const ui = useUiStore()
|
|
|
17
17
|
const board = useBoardStore()
|
|
18
18
|
const documents = useDocumentsStore()
|
|
19
19
|
const toast = useToast()
|
|
20
|
+
const { present } = usePipelineErrorToast()
|
|
20
21
|
|
|
21
22
|
/** Design documents are planned into an existing service; prose documents at the board root. */
|
|
22
23
|
const needsTarget = computed(() => !!ui.spawnPreview && isDesignSource(ui.spawnPreview.source))
|
|
@@ -80,12 +81,7 @@ watch(
|
|
|
80
81
|
// A superseded request's failure is not this selection's failure: toasting it would report an
|
|
81
82
|
// outage against a target whose own plan may be loading fine.
|
|
82
83
|
if (request !== planRequest) return
|
|
83
|
-
|
|
84
|
-
title: t('documents.spawn.planFailed'),
|
|
85
|
-
description: e instanceof Error ? e.message : String(e),
|
|
86
|
-
icon: 'i-lucide-triangle-alert',
|
|
87
|
-
color: 'error',
|
|
88
|
-
})
|
|
84
|
+
present(e, 'documents.spawn.planFailed')
|
|
89
85
|
} finally {
|
|
90
86
|
// Only the current request may clear the spinner; an older one settling first would report
|
|
91
87
|
// "loaded" over a plan still on its way.
|
|
@@ -120,12 +116,7 @@ async function spawn() {
|
|
|
120
116
|
ui.closeSpawnPreview()
|
|
121
117
|
ui.closeDocumentImport()
|
|
122
118
|
} catch (e) {
|
|
123
|
-
|
|
124
|
-
title: t('documents.spawn.spawnFailed'),
|
|
125
|
-
description: e instanceof Error ? e.message : String(e),
|
|
126
|
-
icon: 'i-lucide-triangle-alert',
|
|
127
|
-
color: 'error',
|
|
128
|
-
})
|
|
119
|
+
present(e, 'documents.spawn.spawnFailed')
|
|
129
120
|
} finally {
|
|
130
121
|
spawning.value = false
|
|
131
122
|
}
|
|
@@ -21,6 +21,7 @@ import FoundationalContractSummary from '~/components/foundational/FoundationalC
|
|
|
21
21
|
|
|
22
22
|
const catalog = useFoundationalServicesStore()
|
|
23
23
|
const toast = useToast()
|
|
24
|
+
const { present } = usePipelineErrorToast()
|
|
24
25
|
const { t } = useI18n()
|
|
25
26
|
|
|
26
27
|
// Exhaustive maps of literal `t(...)` keys, so a new tier/format fails the typed-key guard.
|
|
@@ -41,15 +42,6 @@ const tierColor = {
|
|
|
41
42
|
workspace: 'primary',
|
|
42
43
|
} as const satisfies Record<FoundationalServiceTier, string>
|
|
43
44
|
|
|
44
|
-
function notifyError(title: string, e: unknown) {
|
|
45
|
-
toast.add({
|
|
46
|
-
title,
|
|
47
|
-
description: e instanceof Error ? e.message : String(e),
|
|
48
|
-
icon: 'i-lucide-triangle-alert',
|
|
49
|
-
color: 'error',
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
45
|
const busyRows = reactive(new Set<string>())
|
|
54
46
|
const rowBusy = (key: string) => busyRows.has(key)
|
|
55
47
|
async function withRow(key: string, fn: () => Promise<void>) {
|
|
@@ -75,7 +67,7 @@ async function toggleContracts(serviceId: string) {
|
|
|
75
67
|
await catalog.contractsFor(serviceId)
|
|
76
68
|
expanded.value = [...expanded.value, serviceId]
|
|
77
69
|
} catch (e) {
|
|
78
|
-
|
|
70
|
+
present(e, 'foundational.toast.contractsFailed')
|
|
79
71
|
}
|
|
80
72
|
})
|
|
81
73
|
}
|
|
@@ -86,7 +78,7 @@ async function suppress(serviceId: string) {
|
|
|
86
78
|
await catalog.suppress(serviceId)
|
|
87
79
|
toast.add({ title: t('foundational.toast.suppressed'), icon: 'i-lucide-eye-off' })
|
|
88
80
|
} catch (e) {
|
|
89
|
-
|
|
81
|
+
present(e, 'foundational.toast.suppressFailed')
|
|
90
82
|
}
|
|
91
83
|
})
|
|
92
84
|
}
|
|
@@ -28,6 +28,7 @@ const catalog =
|
|
|
28
28
|
? useFoundationalServicesStore()
|
|
29
29
|
: useFoundationalServices(props.kind, props.ownerId)
|
|
30
30
|
const toast = useToast()
|
|
31
|
+
const { present } = usePipelineErrorToast()
|
|
31
32
|
const { t } = useI18n()
|
|
32
33
|
const { confirm } = useConfirm()
|
|
33
34
|
|
|
@@ -44,15 +45,6 @@ const formatItems = computed(() =>
|
|
|
44
45
|
})),
|
|
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 editor ------------------------------------------------------------
|
|
57
49
|
/** null = closed; '' = creating; a service id = editing that row. */
|
|
58
50
|
const editing = ref<string | null>(null)
|
|
@@ -148,7 +140,7 @@ async function save() {
|
|
|
148
140
|
}
|
|
149
141
|
editing.value = null
|
|
150
142
|
} catch (e) {
|
|
151
|
-
|
|
143
|
+
present(e, 'foundational.toast.saveFailed')
|
|
152
144
|
} finally {
|
|
153
145
|
saving.value = false
|
|
154
146
|
}
|
|
@@ -177,7 +169,7 @@ async function remove(service: FoundationalService) {
|
|
|
177
169
|
await catalog.remove(service.id)
|
|
178
170
|
toast.add({ title: t('foundational.toast.deleted'), icon: 'i-lucide-trash-2' })
|
|
179
171
|
} catch (e) {
|
|
180
|
-
|
|
172
|
+
present(e, 'foundational.toast.deleteFailed')
|
|
181
173
|
}
|
|
182
174
|
}
|
|
183
175
|
</script>
|