@cat-factory/app 0.311.1 → 0.311.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -5
- package/app/app.config.ts +6 -18
- package/app/components/assistant/AssistantModal.vue +2 -5
- package/app/components/auth/AuthGate.vue +2 -1
- package/app/components/binaryCandidates/BinaryCandidatesWindow.vue +14 -15
- package/app/components/binaryOutput/StoredAssetView.vue +1 -3
- package/app/components/board/AddTaskModal.vue +3 -10
- package/app/components/board/AgentFailureCard.vue +4 -5
- package/app/components/board/AgentStopButton.vue +5 -7
- package/app/components/board/BoardCanvas.vue +31 -30
- package/app/components/board/CreateInitiativeModal.vue +2 -4
- package/app/components/board/RecurringPipelineModal.vue +2 -7
- package/app/components/board/nodes/AgentChip.vue +3 -2
- package/app/components/board/nodes/BlockNode.vue +14 -11
- package/app/components/board/nodes/InitiativeCard.vue +6 -3
- package/app/components/bootstrap/AdoptionReviewModal.vue +4 -7
- package/app/components/bootstrap/BootstrapModal.vue +6 -15
- package/app/components/bootstrap/BootstrapRunSteps.vue +3 -1
- package/app/components/brainstorm/BrainstormWindow.vue +32 -26
- package/app/components/bugFishing/BugFishingWindow.vue +15 -13
- package/app/components/clarity/ClarityReviewWindow.vue +31 -26
- package/app/components/common/AsyncViewError.vue +1 -1
- package/app/components/common/ClarificationItem.vue +3 -1
- package/app/components/common/EmptyState.vue +41 -19
- package/app/components/common/InterviewGateNotice.vue +3 -1
- package/app/components/common/Spinner.vue +28 -0
- package/app/components/consensus/ConsensusSessionWindow.vue +5 -5
- package/app/components/docs/DocInterviewWindow.vue +6 -6
- package/app/components/documents/ContextDocumentPicker.vue +2 -2
- package/app/components/documents/DocumentImportModal.vue +9 -8
- package/app/components/documents/DocumentSourceConnectModal.vue +10 -10
- package/app/components/documents/DocumentTemplatesModal.vue +15 -9
- package/app/components/documents/RepoContextDocPicker.vue +2 -2
- package/app/components/documents/SpawnPreviewModal.vue +4 -6
- package/app/components/documents/StartFromDesignModal.vue +2 -1
- package/app/components/documents/TaskContextDocs.vue +4 -5
- package/app/components/environments/EnvironmentStatusPanel.vue +11 -8
- package/app/components/environments/steps/EnvPickStep.vue +7 -3
- package/app/components/environments/steps/EnvPreflightStep.vue +6 -5
- package/app/components/focus/BlockFocusView.vue +6 -5
- package/app/components/followUp/FollowUpWindow.vue +7 -9
- package/app/components/forkDecision/ForkDecisionWindow.vue +9 -8
- package/app/components/foundational/FoundationalServiceCatalogList.vue +9 -5
- package/app/components/foundational/FoundationalServiceRegistry.vue +11 -7
- package/app/components/foundational/FoundationalServiceSources.vue +20 -22
- package/app/components/foundational/FoundationalSuppressions.vue +2 -2
- package/app/components/foundational/ServiceCatalogConnection.vue +15 -15
- package/app/components/fragments/FragmentLibraryManager.vue +36 -34
- package/app/components/fragments/FragmentSelector.vue +7 -3
- package/app/components/gates/GateResultView.vue +6 -9
- package/app/components/github/AddServiceFromRepoModal.vue +6 -10
- package/app/components/github/GitHubConnect.vue +10 -17
- package/app/components/github/GitHubPanel.vue +44 -47
- package/app/components/github/RepoTreeBrowser.vue +9 -5
- package/app/components/guidedReview/GuidedReviewModal.vue +16 -6
- package/app/components/guidedReview/GuidedReviewOverview.vue +2 -1
- package/app/components/guidedReview/GuidedReviewPicker.vue +10 -7
- package/app/components/guidedReview/GuidedReviewThread.vue +9 -4
- package/app/components/humanTest/HumanTestWindow.vue +2 -1
- package/app/components/initiative/InitiativePlanningWindow.vue +6 -6
- package/app/components/initiative/InitiativeTrackerWindow.vue +11 -11
- package/app/components/judge/JudgeResultView.vue +2 -7
- package/app/components/kaizen/KaizenPanel.vue +20 -5
- package/app/components/kaizen/KaizenStepStatus.vue +2 -1
- package/app/components/layout/AccountAuditLog.vue +11 -3
- package/app/components/layout/AccountDeploymentSettings.vue +17 -62
- package/app/components/layout/AccountFailureKindRules.vue +6 -5
- package/app/components/layout/AccountModelPolicySettings.vue +2 -6
- package/app/components/layout/AccountPlatformAlertSettings.vue +5 -13
- package/app/components/layout/AccountRiskPolicySettings.vue +13 -14
- package/app/components/layout/AccountRunCredentialSettings.vue +2 -2
- package/app/components/layout/AccountTeamSettings.vue +12 -9
- package/app/components/layout/BoardSwitcher.vue +2 -2
- package/app/components/layout/BoardToolbar.vue +3 -11
- package/app/components/layout/CommandBar.vue +7 -3
- package/app/components/layout/ConnectionStatusBanner.vue +2 -1
- package/app/components/layout/IntegrationsHub.vue +7 -3
- package/app/components/layout/ModelProvidersHub.vue +7 -3
- package/app/components/layout/NotificationsInbox.vue +3 -11
- package/app/components/layout/SideBar.vue +1 -1
- package/app/components/layout/WorkspaceMembersSettings.vue +12 -5
- package/app/components/media/ArtifactLightbox.vue +4 -3
- package/app/components/notifications/NotificationSettingsPanel.vue +4 -11
- package/app/components/observability/ToolCallList.vue +12 -17
- package/app/components/panels/AgentStepDetail.vue +5 -5
- package/app/components/panels/GenericStructuredResultView.vue +6 -9
- package/app/components/panels/InspectorPanel.vue +2 -1
- package/app/components/panels/MergerResultView.vue +5 -6
- package/app/components/panels/ObservabilityPanel.vue +26 -41
- package/app/components/panels/OperatorDashboardPanel.vue +29 -17
- package/app/components/panels/ReportsPanel.vue +17 -11
- package/app/components/panels/ReportsSpendBreakdown.vue +7 -3
- package/app/components/panels/ResultWindowShell.vue +1 -1
- package/app/components/panels/RunDetailLoadState.vue +4 -3
- package/app/components/panels/StepContainerStatus.vue +4 -3
- package/app/components/panels/StepDelegatedStatus.vue +3 -2
- package/app/components/panels/StepMetadataCard.vue +6 -8
- package/app/components/panels/StepRunMeta.vue +2 -5
- package/app/components/panels/inspector/ContainerSummary.vue +7 -1
- package/app/components/panels/inspector/EpicChildren.vue +7 -3
- package/app/components/panels/inspector/FrontendConfig.vue +7 -3
- package/app/components/panels/inspector/InitiativeInspector.vue +5 -2
- package/app/components/panels/inspector/ServiceConnections.vue +7 -3
- package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +2 -6
- package/app/components/panels/inspector/ServiceTestSecrets.vue +2 -6
- package/app/components/panels/inspector/ServiceTestingContext.vue +2 -6
- package/app/components/panels/inspector/ServiceValidationConfig.vue +38 -39
- package/app/components/panels/inspector/TaskExecution.vue +5 -6
- package/app/components/panels/inspector/TaskRunSettings.vue +7 -3
- package/app/components/panels/inspector/TaskStructure.vue +0 -1
- package/app/components/pipeline/AgentPromptEditor.vue +25 -18
- package/app/components/pipeline/PipelineBuilder.vue +14 -24
- package/app/components/pipeline/PipelineProgress.vue +16 -17
- package/app/components/prReview/PrReviewPhaseBadge.vue +3 -2
- package/app/components/prReview/PrReviewWindow.vue +16 -20
- package/app/components/providers/ApiKeysSection.vue +2 -6
- package/app/components/providers/PersonalCredentialModal.vue +2 -6
- package/app/components/providers/PersonalSubscriptionSection.vue +3 -7
- package/app/components/providers/VendorCredentialsModal.vue +2 -6
- package/app/components/provisioning/ProvisioningLogsDrawer.vue +10 -6
- package/app/components/ralph/RalphLoopResultView.vue +2 -7
- package/app/components/requirements/RequirementsReviewWindow.vue +44 -39
- package/app/components/sandbox/SandboxPanel.vue +37 -25
- package/app/components/settings/AccountSettingsPanel.vue +6 -3
- package/app/components/settings/ApiTokensPanel.vue +2 -6
- package/app/components/settings/BudgetSettings.vue +2 -2
- package/app/components/settings/CapabilityCredentialsPanel.vue +2 -6
- package/app/components/settings/CloudflareHandlerSection.vue +3 -7
- package/app/components/settings/ConsensusGroupsSection.vue +7 -3
- package/app/components/settings/CustomManifestTypeEditor.vue +7 -3
- package/app/components/settings/DefaultProvisionTypeSection.vue +2 -2
- package/app/components/settings/InfraHandlersConfigurator.vue +8 -11
- package/app/components/settings/IssueTrackerPanel.vue +9 -9
- package/app/components/settings/LocalContainerPoolSettings.vue +2 -6
- package/app/components/settings/LocalModelEndpointsPanel.vue +5 -7
- package/app/components/settings/McpAuthorizeScreen.vue +8 -3
- package/app/components/settings/McpOAuthCallbackScreen.vue +2 -1
- package/app/components/settings/ModelConfigurationPanel.vue +45 -17
- package/app/components/settings/ObservabilityConnectionPanel.vue +4 -15
- package/app/components/settings/OpenRouterCatalogPanel.vue +8 -24
- package/app/components/settings/PackageRegistriesPanel.vue +2 -6
- package/app/components/settings/ProviderConnectionTab.vue +3 -7
- package/app/components/settings/RiskPolicyPanel.vue +4 -16
- package/app/components/settings/SharedStacksPanel.vue +8 -18
- package/app/components/settings/TaskTypeSuppressionsPanel.vue +10 -6
- package/app/components/settings/UsageSettings.vue +11 -2
- package/app/components/settings/UserSecretsSection.vue +5 -7
- package/app/components/settings/WorkspaceMetadataSettings.vue +9 -9
- package/app/components/settings/WorkspaceSettingsPanel.vue +3 -11
- package/app/components/skills/ReviewSkillQueue.vue +7 -3
- package/app/components/skills/SkillLibraryManager.vue +20 -18
- package/app/components/slack/SlackPanel.vue +5 -8
- package/app/components/spec/ServiceSpecWindow.vue +32 -29
- package/app/components/tasks/BugHuntModal.vue +23 -19
- package/app/components/tasks/ContextIssuePicker.vue +2 -2
- package/app/components/tasks/TaskContextIssues.vue +4 -5
- package/app/components/tasks/TaskImportModal.vue +10 -8
- package/app/components/tasks/TaskSourceConnectModal.vue +11 -11
- package/app/components/testing/TestReportWindow.vue +6 -12
- package/app/components/tutorial/TutorialCatalogue.vue +7 -3
- package/app/components/tutorial/TutorialOverlay.vue +2 -1
- package/app/components/tutorial/TutorialPrompt.vue +7 -3
- package/app/components/vcs/GitLabConnect.vue +2 -6
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +9 -4
- package/app/composables/toastTitleKeys.spec.ts +143 -0
- package/app/composables/useActionToast.spec.ts +71 -0
- package/app/composables/useActionToast.ts +101 -0
- package/app/composables/useBlockDeletion.ts +3 -6
- package/app/composables/useConfirmAction.ts +5 -3
- package/app/composables/useContextLinking.spec.ts +3 -0
- package/app/composables/useContextLinking.ts +11 -16
- package/app/composables/useCopyToClipboard.ts +4 -3
- package/app/composables/useNavContributions.ts +4 -5
- package/app/composables/usePipelineErrorToast.spec.ts +24 -0
- package/app/composables/usePipelineErrorToast.ts +26 -2
- package/app/composables/usePipelineLibraryActions.ts +8 -9
- package/app/pages/index.vue +3 -2
- package/app/stores/board/context.ts +4 -3
- package/app/stores/board/placement.ts +8 -20
- package/app/stores/board/removal.ts +4 -13
- package/app/stores/board.spec.ts +10 -1
- package/app/stores/board.ts +3 -2
- package/app/utils/bootstrapSteps.ts +11 -2
- package/app/utils/pipelineRender.ts +21 -8
- package/i18n/locales/de.json +12 -30
- package/i18n/locales/en.json +12 -30
- package/i18n/locales/es.json +12 -30
- package/i18n/locales/fr.json +12 -30
- package/i18n/locales/he.json +12 -30
- package/i18n/locales/it.json +12 -30
- package/i18n/locales/ja.json +12 -30
- package/i18n/locales/pl.json +12 -30
- package/i18n/locales/tr.json +12 -30
- package/i18n/locales/uk.json +12 -30
- package/package.json +1 -1
|
@@ -29,8 +29,8 @@ const bootstrap = useBootstrapStore()
|
|
|
29
29
|
const agentRuns = useAgentRunsStore()
|
|
30
30
|
const github = useGitHubStore()
|
|
31
31
|
const board = useBoardStore()
|
|
32
|
-
const
|
|
33
|
-
const { present } = usePipelineErrorToast()
|
|
32
|
+
const actionToast = useActionToast()
|
|
33
|
+
const { present, presentReported } = usePipelineErrorToast()
|
|
34
34
|
const { freeFramePosition, focusFrame } = useFramePlacement()
|
|
35
35
|
const { t } = useI18n()
|
|
36
36
|
const { confirmAction, toastDone } = useConfirmAction()
|
|
@@ -367,11 +367,8 @@ async function openCreateRepo() {
|
|
|
367
367
|
private: isPrivate.value,
|
|
368
368
|
description: description.value.trim() || undefined,
|
|
369
369
|
})
|
|
370
|
-
toast.
|
|
371
|
-
title: t('bootstrap.toast.repoCreated'),
|
|
370
|
+
actionToast.success('bootstrap.toast.repoCreated', {
|
|
372
371
|
description: `${repo.owner}/${repo.name}`,
|
|
373
|
-
icon: 'i-lucide-check',
|
|
374
|
-
color: 'success',
|
|
375
372
|
})
|
|
376
373
|
} catch (e) {
|
|
377
374
|
present(e, 'bootstrap.toast.repoCreateFailed')
|
|
@@ -426,26 +423,20 @@ async function launch() {
|
|
|
426
423
|
if (job.status === 'failed') {
|
|
427
424
|
// The container couldn't even start (pre-flight failure, e.g. the target
|
|
428
425
|
// repo isn't empty) — surfaced synchronously, before any board frame.
|
|
429
|
-
toast.
|
|
430
|
-
|
|
431
|
-
description: job.error ?? t('bootstrap.toast.failedFallback'),
|
|
432
|
-
icon: 'i-lucide-triangle-alert',
|
|
433
|
-
color: 'error',
|
|
426
|
+
presentReported(job.error, 'bootstrap.toast.failed', {
|
|
427
|
+
descriptionKey: 'bootstrap.toast.failedFallback',
|
|
434
428
|
})
|
|
435
429
|
} else {
|
|
436
430
|
// Running: the container is spinning up. A provisional service card now
|
|
437
431
|
// shows on the board and tracks live progress; the run continues in the
|
|
438
432
|
// background and becomes a real, droppable service when it finishes.
|
|
439
|
-
toast.
|
|
440
|
-
title: t('bootstrap.toast.started'),
|
|
433
|
+
actionToast.info('bootstrap.toast.started', {
|
|
441
434
|
// A monorepo run does not run straight through: it surveys, then waits for the
|
|
442
435
|
// reviewer. Saying "bootstrapping…" there would set the wrong expectation about who
|
|
443
436
|
// the next move belongs to.
|
|
444
437
|
description: job.monorepo
|
|
445
438
|
? t('bootstrap.toast.startedMonorepoDesc', { directory: job.monorepo.directory })
|
|
446
439
|
: t('bootstrap.toast.startedDesc', { repo: job.repoName }),
|
|
447
|
-
icon: 'i-lucide-loader-circle',
|
|
448
|
-
color: 'info',
|
|
449
440
|
})
|
|
450
441
|
repoName.value = ''
|
|
451
442
|
description.value = ''
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// are one rule, not two. How a state RENDERS is `BOOTSTRAP_STEP_STYLE`, beside the vocabulary
|
|
13
13
|
// it is keyed by.
|
|
14
14
|
import type { BootstrapStepId, BootstrapStepState } from '@cat-factory/contracts'
|
|
15
|
+
import Spinner from '~/components/common/Spinner.vue'
|
|
15
16
|
|
|
16
17
|
const props = defineProps<{ runId: string }>()
|
|
17
18
|
|
|
@@ -38,8 +39,9 @@ function stateLabel(state: BootstrapStepState): string {
|
|
|
38
39
|
:data-step="step.id"
|
|
39
40
|
:data-state="step.state"
|
|
40
41
|
>
|
|
41
|
-
<
|
|
42
|
+
<Spinner
|
|
42
43
|
:name="BOOTSTRAP_STEP_STYLE[step.state].icon"
|
|
44
|
+
:spinning="BOOTSTRAP_STEP_STYLE[step.state].spin"
|
|
43
45
|
class="mt-px h-3 w-3 shrink-0"
|
|
44
46
|
:class="BOOTSTRAP_STEP_STYLE[step.state].iconClass"
|
|
45
47
|
/>
|
|
@@ -20,10 +20,12 @@ import type {
|
|
|
20
20
|
ReviewItemStatus,
|
|
21
21
|
} from '~/types/brainstorm'
|
|
22
22
|
import SectionLabel from '~/components/common/SectionLabel.vue'
|
|
23
|
+
import Spinner from '~/components/common/Spinner.vue'
|
|
24
|
+
import EmptyState from '~/components/common/EmptyState.vue'
|
|
23
25
|
|
|
24
26
|
const board = useBoardStore()
|
|
25
27
|
const brainstorm = useBrainstormStore()
|
|
26
|
-
const
|
|
28
|
+
const actionToast = useActionToast()
|
|
27
29
|
const { present } = usePipelineErrorToast()
|
|
28
30
|
const { t } = useI18n()
|
|
29
31
|
const access = useWorkspaceAccess()
|
|
@@ -194,10 +196,9 @@ async function incorporate(feedback?: string) {
|
|
|
194
196
|
}
|
|
195
197
|
redoComment.value = ''
|
|
196
198
|
showRedo.value = false
|
|
197
|
-
toast.
|
|
198
|
-
|
|
199
|
+
actionToast.info('brainstorm.toast.draftingTitle', {
|
|
200
|
+
params: { doc: docNoun.value },
|
|
199
201
|
description: t('brainstorm.toast.draftingDescription'),
|
|
200
|
-
icon: 'i-lucide-wand-sparkles',
|
|
201
202
|
})
|
|
202
203
|
close()
|
|
203
204
|
}
|
|
@@ -207,15 +208,16 @@ async function reReview() {
|
|
|
207
208
|
try {
|
|
208
209
|
const updated = await brainstorm.reReview(blockId.value, activeStage.value)
|
|
209
210
|
const newCount = brainstorm.openCount(updated)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
if (updated.status === 'incorporated') {
|
|
212
|
+
actionToast.success('brainstorm.toast.reReviewSettled')
|
|
213
|
+
} else if (updated.status === 'exceeded') {
|
|
214
|
+
actionToast.warning('brainstorm.toast.reReviewExceeded')
|
|
215
|
+
} else {
|
|
216
|
+
actionToast.info('brainstorm.toast.reReviewNewOptions', {
|
|
217
|
+
params: { count: newCount },
|
|
218
|
+
plural: newCount,
|
|
219
|
+
})
|
|
220
|
+
}
|
|
219
221
|
} catch (e) {
|
|
220
222
|
present(e, 'brainstorm.toast.reReviewError')
|
|
221
223
|
}
|
|
@@ -226,7 +228,7 @@ async function proceed() {
|
|
|
226
228
|
acting.value = true
|
|
227
229
|
try {
|
|
228
230
|
await brainstorm.proceed(blockId.value, activeStage.value)
|
|
229
|
-
|
|
231
|
+
actionToast.info('brainstorm.toast.proceeding')
|
|
230
232
|
} catch (e) {
|
|
231
233
|
present(e, 'brainstorm.toast.proceedError')
|
|
232
234
|
} finally {
|
|
@@ -240,12 +242,12 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
240
242
|
try {
|
|
241
243
|
await brainstorm.resolveExceeded(blockId.value, activeStage.value, choice)
|
|
242
244
|
if (choice === 'stop-reset') {
|
|
243
|
-
|
|
245
|
+
actionToast.info('brainstorm.toast.taskReset')
|
|
244
246
|
close()
|
|
245
247
|
} else if (choice === 'proceed') {
|
|
246
|
-
|
|
248
|
+
actionToast.info('brainstorm.toast.proceeding')
|
|
247
249
|
} else {
|
|
248
|
-
|
|
250
|
+
actionToast.info('brainstorm.toast.extraRoundGranted')
|
|
249
251
|
}
|
|
250
252
|
} catch (e) {
|
|
251
253
|
present(e, 'brainstorm.toast.resolveError')
|
|
@@ -291,20 +293,24 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
291
293
|
</p>
|
|
292
294
|
|
|
293
295
|
<!-- empty state -->
|
|
294
|
-
<
|
|
296
|
+
<EmptyState
|
|
295
297
|
v-if="!session && !busy && !loading"
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
icon="i-lucide-lightbulb"
|
|
299
|
+
:title="t('brainstorm.empty')"
|
|
300
|
+
/>
|
|
301
|
+
|
|
302
|
+
<!-- initial fetch on open -->
|
|
303
|
+
<div v-else-if="loading && !busy && !session" class="flex flex-col gap-3">
|
|
304
|
+
<USkeleton v-for="n in 3" :key="n" class="h-20 w-full rounded-lg" />
|
|
299
305
|
</div>
|
|
300
306
|
|
|
301
|
-
<!--
|
|
307
|
+
<!-- an agent pass running -->
|
|
302
308
|
<div
|
|
303
|
-
v-else-if="
|
|
309
|
+
v-else-if="busy && !session"
|
|
304
310
|
class="flex items-center justify-center gap-2 p-8 text-sm text-muted"
|
|
305
311
|
>
|
|
306
|
-
<
|
|
307
|
-
{{
|
|
312
|
+
<Spinner class="h-4 w-4" />
|
|
313
|
+
{{ t('brainstorm.generating') }}
|
|
308
314
|
</div>
|
|
309
315
|
|
|
310
316
|
<template v-else-if="session">
|
|
@@ -332,7 +338,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
332
338
|
v-else-if="working"
|
|
333
339
|
class="mb-4 flex items-center gap-2 rounded-lg border border-app-warning-900/60 bg-app-warning-950/30 p-4 text-sm text-app-warning-200"
|
|
334
340
|
>
|
|
335
|
-
<
|
|
341
|
+
<Spinner class="h-5 w-5 shrink-0" />
|
|
336
342
|
<span v-if="incorporating">
|
|
337
343
|
{{ t('brainstorm.working.incorporating', { doc: docNoun }) }}
|
|
338
344
|
</span>
|
|
@@ -29,6 +29,8 @@ import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
|
|
|
29
29
|
import StepRunMeta from '~/components/panels/StepRunMeta.vue'
|
|
30
30
|
import MarkdownProse from '~/components/common/MarkdownProse.vue'
|
|
31
31
|
import SectionLabel from '~/components/common/SectionLabel.vue'
|
|
32
|
+
import Spinner from '~/components/common/Spinner.vue'
|
|
33
|
+
import EmptyState from '~/components/common/EmptyState.vue'
|
|
32
34
|
|
|
33
35
|
const execution = useExecutionStore()
|
|
34
36
|
const board = useBoardStore()
|
|
@@ -304,11 +306,12 @@ const PHASE_ICON: Record<string, string> = {
|
|
|
304
306
|
:data-testid="`bug-fishing-phase-${phase.id}`"
|
|
305
307
|
@click="selectedPassKey = passKey(phase)"
|
|
306
308
|
>
|
|
307
|
-
<
|
|
309
|
+
<Spinner
|
|
308
310
|
:name="PHASE_ICON[phase.status] ?? 'i-lucide-circle-dashed'"
|
|
311
|
+
:spinning="phase.status === 'fishing'"
|
|
309
312
|
class="mt-0.5 h-3.5 w-3.5 shrink-0"
|
|
310
313
|
:class="{
|
|
311
|
-
'
|
|
314
|
+
'text-app-info-300': phase.status === 'fishing',
|
|
312
315
|
'text-app-success-400': phase.status === 'completed',
|
|
313
316
|
'text-app-warning-400': phase.status === 'failed',
|
|
314
317
|
'text-app-600': phase.status === 'pending',
|
|
@@ -379,7 +382,7 @@ const PHASE_ICON: Record<string, string> = {
|
|
|
379
382
|
data-testid="bug-fishing-in-progress"
|
|
380
383
|
class="mb-4 flex items-center gap-2 rounded-lg border border-app-info-500/25 bg-app-info-500/5 px-3 py-2 text-xs text-app-info-200"
|
|
381
384
|
>
|
|
382
|
-
<
|
|
385
|
+
<Spinner class="h-4 w-4 shrink-0" />
|
|
383
386
|
<span>
|
|
384
387
|
{{
|
|
385
388
|
t('bugFishing.stillFishing', {
|
|
@@ -459,20 +462,19 @@ const PHASE_ICON: Record<string, string> = {
|
|
|
459
462
|
{{ bugFishing.error }}
|
|
460
463
|
</p>
|
|
461
464
|
|
|
462
|
-
<!-- An expedition that caught nothing is a real answer
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
<
|
|
465
|
+
<!-- An expedition that caught nothing is a real answer. The copy says which of the two it
|
|
466
|
+
is, because "nothing found" and "nothing left to triage" are different things to be
|
|
467
|
+
told. -->
|
|
468
|
+
<EmptyState
|
|
466
469
|
v-if="visibleFindings.length === 0"
|
|
467
470
|
data-testid="bug-fishing-empty"
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
{{
|
|
471
|
+
:icon="findings.length === 0 ? 'i-lucide-fish' : 'i-lucide-check-check'"
|
|
472
|
+
:title="
|
|
471
473
|
findings.length === 0
|
|
472
474
|
? t('bugFishing.empty.nothingCaught')
|
|
473
475
|
: t('bugFishing.empty.allTriaged')
|
|
474
|
-
|
|
475
|
-
|
|
476
|
+
"
|
|
477
|
+
/>
|
|
476
478
|
|
|
477
479
|
<ul v-else class="space-y-2">
|
|
478
480
|
<li
|
|
@@ -591,7 +593,7 @@ const PHASE_ICON: Record<string, string> = {
|
|
|
591
593
|
data-testid="bug-fishing-finding-spawning"
|
|
592
594
|
class="mt-2 flex flex-wrap items-center gap-2 text-2xs text-muted"
|
|
593
595
|
>
|
|
594
|
-
<
|
|
596
|
+
<Spinner class="h-3.5 w-3.5" />
|
|
595
597
|
<span>{{ t('bugFishing.finding.spawning') }}</span>
|
|
596
598
|
</div>
|
|
597
599
|
|
|
@@ -20,11 +20,13 @@ import type {
|
|
|
20
20
|
ReviewItemStatus,
|
|
21
21
|
} from '~/types/clarity'
|
|
22
22
|
import SectionLabel from '~/components/common/SectionLabel.vue'
|
|
23
|
+
import Spinner from '~/components/common/Spinner.vue'
|
|
24
|
+
import EmptyState from '~/components/common/EmptyState.vue'
|
|
23
25
|
|
|
24
26
|
const board = useBoardStore()
|
|
25
27
|
const clarity = useClarityStore()
|
|
26
28
|
const models = useModelsStore()
|
|
27
|
-
const
|
|
29
|
+
const actionToast = useActionToast()
|
|
28
30
|
const { present } = usePipelineErrorToast()
|
|
29
31
|
const { t } = useI18n()
|
|
30
32
|
const access = useWorkspaceAccess()
|
|
@@ -233,10 +235,8 @@ async function incorporate(feedback?: string) {
|
|
|
233
235
|
showRedo.value = false
|
|
234
236
|
// The fold + re-review now run in the durable driver. Hand the user back to the board;
|
|
235
237
|
// a notification calls them back only if the re-review needs more input.
|
|
236
|
-
toast.
|
|
237
|
-
title: t('clarity.toast.clarifyingTitle'),
|
|
238
|
+
actionToast.info('clarity.toast.clarifyingTitle', {
|
|
238
239
|
description: t('clarity.toast.clarifyingDescription'),
|
|
239
|
-
icon: 'i-lucide-wand-sparkles',
|
|
240
240
|
})
|
|
241
241
|
close()
|
|
242
242
|
}
|
|
@@ -246,15 +246,16 @@ async function reReview() {
|
|
|
246
246
|
try {
|
|
247
247
|
const updated = await clarity.reReview(blockId.value)
|
|
248
248
|
const newFindings = clarity.openCount(updated)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
249
|
+
if (updated.status === 'incorporated') {
|
|
250
|
+
actionToast.success('clarity.toast.reReviewSatisfied')
|
|
251
|
+
} else if (updated.status === 'exceeded') {
|
|
252
|
+
actionToast.warning('clarity.toast.reReviewExceeded')
|
|
253
|
+
} else {
|
|
254
|
+
actionToast.info('clarity.toast.reReviewNewFindings', {
|
|
255
|
+
params: { count: newFindings },
|
|
256
|
+
plural: newFindings,
|
|
257
|
+
})
|
|
258
|
+
}
|
|
258
259
|
} catch (e) {
|
|
259
260
|
present(e, 'clarity.error.reReview')
|
|
260
261
|
}
|
|
@@ -266,7 +267,7 @@ async function proceed() {
|
|
|
266
267
|
try {
|
|
267
268
|
await flushDrafts()
|
|
268
269
|
await clarity.proceed(blockId.value)
|
|
269
|
-
|
|
270
|
+
actionToast.info('clarity.toast.proceeding')
|
|
270
271
|
} catch (e) {
|
|
271
272
|
present(e, 'clarity.error.proceed')
|
|
272
273
|
} finally {
|
|
@@ -280,12 +281,12 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
280
281
|
try {
|
|
281
282
|
await clarity.resolveExceeded(blockId.value, choice)
|
|
282
283
|
if (choice === 'stop-reset') {
|
|
283
|
-
|
|
284
|
+
actionToast.info('clarity.toast.taskReset')
|
|
284
285
|
close()
|
|
285
286
|
} else if (choice === 'proceed') {
|
|
286
|
-
|
|
287
|
+
actionToast.info('clarity.toast.proceeding')
|
|
287
288
|
} else {
|
|
288
|
-
|
|
289
|
+
actionToast.info('clarity.toast.extraRoundGranted')
|
|
289
290
|
}
|
|
290
291
|
} catch (e) {
|
|
291
292
|
present(e, 'clarity.error.resolve')
|
|
@@ -329,20 +330,24 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
329
330
|
|
|
330
331
|
<!-- empty state — the reviewer runs automatically as the first pipeline
|
|
331
332
|
gate step, so there's nothing to do here until then -->
|
|
332
|
-
<
|
|
333
|
+
<EmptyState
|
|
333
334
|
v-if="!review && !busy && !loading"
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
335
|
+
icon="i-lucide-list-checks"
|
|
336
|
+
:title="t('clarity.empty')"
|
|
337
|
+
/>
|
|
338
|
+
|
|
339
|
+
<!-- initial fetch on open -->
|
|
340
|
+
<div v-else-if="loading && !busy && !review" class="flex flex-col gap-3">
|
|
341
|
+
<USkeleton v-for="n in 3" :key="n" class="h-20 w-full rounded-lg" />
|
|
337
342
|
</div>
|
|
338
343
|
|
|
339
|
-
<!--
|
|
344
|
+
<!-- a reviewer pass running -->
|
|
340
345
|
<div
|
|
341
|
-
v-else-if="
|
|
346
|
+
v-else-if="busy && !review"
|
|
342
347
|
class="flex items-center justify-center gap-2 p-8 text-sm text-muted"
|
|
343
348
|
>
|
|
344
|
-
<
|
|
345
|
-
{{
|
|
349
|
+
<Spinner class="h-4 w-4" />
|
|
350
|
+
{{ t('clarity.triaging') }}
|
|
346
351
|
</div>
|
|
347
352
|
|
|
348
353
|
<template v-else-if="review">
|
|
@@ -371,7 +376,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
371
376
|
v-else-if="working"
|
|
372
377
|
class="mb-4 flex items-center gap-2 rounded-lg border border-primary/60 bg-primary/10 p-4 text-sm text-primary"
|
|
373
378
|
>
|
|
374
|
-
<
|
|
379
|
+
<Spinner class="h-5 w-5 shrink-0" />
|
|
375
380
|
<span v-if="incorporating">
|
|
376
381
|
{{ t('clarity.incorporatingStage') }}
|
|
377
382
|
</span>
|
|
@@ -12,7 +12,7 @@ function reload() {
|
|
|
12
12
|
<!-- safe-area-ok: a scrim with one card centred on it, so no content reaches an edge -->
|
|
13
13
|
<div
|
|
14
14
|
data-testid="async-view-error"
|
|
15
|
-
class="fixed inset-0 z-50 grid place-items-center bg-
|
|
15
|
+
class="fixed inset-0 z-50 grid place-items-center bg-elevated/75 p-6 backdrop-blur-sm"
|
|
16
16
|
role="alert"
|
|
17
17
|
>
|
|
18
18
|
<div class="max-w-sm rounded-2xl border border-muted bg-default p-5 shadow-2xl">
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
// -specific extras (severity/category badges, a window's own recommendation section) ride the
|
|
9
9
|
// `badges` / `actions` slots; the recommend button only EMITS, so each window wires its own
|
|
10
10
|
// recommend mechanism. `dismissed`/`requested` hide the textarea (nothing to answer right now).
|
|
11
|
+
import Spinner from '~/components/common/Spinner.vue'
|
|
12
|
+
|
|
11
13
|
const props = defineProps<{
|
|
12
14
|
/** The question / finding headline. */
|
|
13
15
|
prompt: string
|
|
@@ -82,7 +84,7 @@ const draft = computed({
|
|
|
82
84
|
class="mt-2 inline-flex items-center gap-1 text-2xs text-primary"
|
|
83
85
|
data-testid="clarification-requested"
|
|
84
86
|
>
|
|
85
|
-
<
|
|
87
|
+
<Spinner class="h-3.5 w-3.5" />
|
|
86
88
|
{{ t('clarification.generating') }}
|
|
87
89
|
</div>
|
|
88
90
|
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
2
|
+
// The SPA's one "nothing here yet" render. Every list, panel section and picker that can be empty
|
|
3
|
+
// shows this, so an empty state reads the same everywhere (frontend/app/README.md, "Empty states:
|
|
4
|
+
// `EmptyState`, always").
|
|
5
|
+
//
|
|
6
|
+
// It renders Nuxt UI's `UEmpty`, which owns the structure, the call-to-action row and the theme
|
|
7
|
+
// tokens. This file decides what `UEmpty` leaves to each caller, once:
|
|
8
|
+
//
|
|
9
|
+
// - The `naked` variant: an empty state sits INSIDE a panel that already draws its own border, so
|
|
10
|
+
// `UEmpty`'s default outline would draw a second box.
|
|
11
|
+
// - The spacing. `UEmpty`'s root pads `p-4 sm:p-6 lg:p-8` with a `gap-4`, sized for a page body.
|
|
12
|
+
// Every responsive step is reset here, because overriding the base `p-4` alone leaves 32px of
|
|
13
|
+
// padding inside a narrow picker at desktop widths.
|
|
14
|
+
// - The header markup. `UEmpty` renders its title as an `<h2>`, which would put a level-2 heading
|
|
15
|
+
// into every picker, dropdown and inspector section. The header slot renders the copy as plain
|
|
16
|
+
// text instead.
|
|
17
|
+
//
|
|
18
|
+
// The default slot is the call-to-action: a site with a button to fill the empty list passes it
|
|
19
|
+
// here. Dumb by design: it takes already-resolved copy and an icon.
|
|
7
20
|
withDefaults(
|
|
8
21
|
defineProps<{
|
|
9
22
|
icon?: string
|
|
@@ -11,25 +24,34 @@ withDefaults(
|
|
|
11
24
|
description?: string
|
|
12
25
|
compact?: boolean
|
|
13
26
|
}>(),
|
|
14
|
-
{ icon: 'i-lucide-inbox', compact: false },
|
|
27
|
+
{ icon: 'i-lucide-inbox', description: undefined, compact: false },
|
|
15
28
|
)
|
|
16
29
|
</script>
|
|
17
30
|
|
|
18
31
|
<template>
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
:
|
|
32
|
+
<UEmpty
|
|
33
|
+
variant="naked"
|
|
34
|
+
:ui="{
|
|
35
|
+
root: compact
|
|
36
|
+
? 'gap-1 p-0 py-4 sm:p-0 sm:py-4 lg:p-0 lg:py-4'
|
|
37
|
+
: 'gap-2 p-0 py-10 sm:p-0 sm:py-10 lg:p-0 lg:py-10',
|
|
38
|
+
header: compact ? 'gap-1' : 'gap-2',
|
|
39
|
+
}"
|
|
22
40
|
data-testid="empty-state"
|
|
23
41
|
>
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
{{ title }}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
<template #header>
|
|
43
|
+
<UIcon :name="icon" :class="compact ? 'size-5' : 'size-8'" class="text-app-600" />
|
|
44
|
+
<p :class="compact ? 'text-xs' : 'text-sm'" class="font-medium text-muted">{{ title }}</p>
|
|
45
|
+
<p
|
|
46
|
+
v-if="description"
|
|
47
|
+
:class="compact ? 'text-2xs' : 'text-xs'"
|
|
48
|
+
class="text-balance text-dimmed"
|
|
49
|
+
>
|
|
50
|
+
{{ description }}
|
|
51
|
+
</p>
|
|
52
|
+
</template>
|
|
53
|
+
<template v-if="$slots.default" #actions>
|
|
32
54
|
<slot />
|
|
33
|
-
</
|
|
34
|
-
</
|
|
55
|
+
</template>
|
|
56
|
+
</UEmpty>
|
|
35
57
|
</template>
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// proceed read as dead buttons. Copy is per-feature (passed in); the treatment is shared, so the
|
|
7
7
|
// two windows can't drift on how a wait or a failure looks. See
|
|
8
8
|
// `docs/initiatives/clarification-items.md`.
|
|
9
|
+
import Spinner from '~/components/common/Spinner.vue'
|
|
10
|
+
|
|
9
11
|
defineProps<{
|
|
10
12
|
/** `working` = a pass is in flight (spinner); `failed` = the run stopped (error treatment). */
|
|
11
13
|
variant: 'working' | 'failed'
|
|
@@ -24,7 +26,7 @@ defineProps<{
|
|
|
24
26
|
class="flex flex-col items-center gap-2 rounded-lg border border-default bg-app-950/40 p-6 text-center"
|
|
25
27
|
:data-testid="testid"
|
|
26
28
|
>
|
|
27
|
-
<
|
|
29
|
+
<Spinner class="h-5 w-5 text-primary" />
|
|
28
30
|
<p class="text-sm text-default">{{ title }}</p>
|
|
29
31
|
<p class="text-xs text-muted">{{ hint }}</p>
|
|
30
32
|
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The one place `animate-spin` is written (`scripts/check-frontend-feedback.mjs` enforces it).
|
|
3
|
+
//
|
|
4
|
+
// Use it only where no Nuxt UI component carries the state: `UButton`, `UInput`, `USelectMenu` and
|
|
5
|
+
// the rest take `:loading`, and that prop is the answer there. This is for a bare icon that shows
|
|
6
|
+
// work in progress: a step-status glyph, a "loading…" line, a full-panel wait.
|
|
7
|
+
//
|
|
8
|
+
// `spinning` lets a status icon keep ONE element across its states (a step glyph that spins while
|
|
9
|
+
// it runs and stops when it settles) instead of two branches. `name` defaults to Nuxt UI's own
|
|
10
|
+
// loading icon, so a plain spinner matches the one `:loading` draws inside a button. Size and
|
|
11
|
+
// colour are the caller's classes, which land on the icon.
|
|
12
|
+
//
|
|
13
|
+
// `motion-safe:` so a reader who asked the OS for reduced motion sees the icon still; the state
|
|
14
|
+
// is carried by the icon itself and its surrounding copy, never by the motion alone.
|
|
15
|
+
withDefaults(defineProps<{ name?: string; spinning?: boolean }>(), {
|
|
16
|
+
name: undefined,
|
|
17
|
+
spinning: true,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const appConfig = useAppConfig()
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<UIcon
|
|
25
|
+
:name="name ?? appConfig.ui.icons.loading"
|
|
26
|
+
:class="spinning ? 'motion-safe:animate-spin' : ''"
|
|
27
|
+
/>
|
|
28
|
+
</template>
|
|
@@ -13,6 +13,7 @@ import MarkdownProse from '~/components/common/MarkdownProse.vue'
|
|
|
13
13
|
import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
|
|
14
14
|
import { agentKindMeta } from '~/utils/catalog'
|
|
15
15
|
import SectionLabel from '~/components/common/SectionLabel.vue'
|
|
16
|
+
import EmptyState from '~/components/common/EmptyState.vue'
|
|
16
17
|
|
|
17
18
|
const { t, n } = useI18n()
|
|
18
19
|
|
|
@@ -145,12 +146,11 @@ function topScore(c: ConsensusContribution): { label: string; value: number } |
|
|
|
145
146
|
</template>
|
|
146
147
|
|
|
147
148
|
<div class="flex-1 overflow-y-auto px-6 py-5">
|
|
148
|
-
<div v-if="loading && !session" class="
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<div v-else-if="!session" class="py-16 text-center text-sm text-dimmed">
|
|
152
|
-
{{ t('consensus.empty') }}
|
|
149
|
+
<div v-if="loading && !session" class="space-y-4">
|
|
150
|
+
<USkeleton class="h-32 w-full rounded-lg" />
|
|
151
|
+
<USkeleton v-for="n in 3" :key="n" class="h-16 w-full rounded-lg" />
|
|
153
152
|
</div>
|
|
153
|
+
<EmptyState v-else-if="!session" icon="i-lucide-users" :title="t('consensus.empty')" />
|
|
154
154
|
<template v-else>
|
|
155
155
|
<!-- failure -->
|
|
156
156
|
<div
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
interviewStepReached,
|
|
26
26
|
} from '~/utils/interviewGate'
|
|
27
27
|
import SectionLabel from '~/components/common/SectionLabel.vue'
|
|
28
|
+
import EmptyState from '~/components/common/EmptyState.vue'
|
|
28
29
|
|
|
29
30
|
const board = useBoardStore()
|
|
30
31
|
const docInterview = useDocInterviewStore()
|
|
@@ -123,13 +124,12 @@ const onProceed = () =>
|
|
|
123
124
|
|
|
124
125
|
<div class="min-h-0 flex-1 overflow-y-auto px-5 py-4">
|
|
125
126
|
<!-- No session yet -->
|
|
126
|
-
<
|
|
127
|
+
<EmptyState
|
|
127
128
|
v-if="!session"
|
|
128
|
-
class="
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
</div>
|
|
129
|
+
class="h-full"
|
|
130
|
+
icon="i-lucide-messages-square"
|
|
131
|
+
:title="t('docInterview.empty')"
|
|
132
|
+
/>
|
|
133
133
|
|
|
134
134
|
<template v-else>
|
|
135
135
|
<p class="mb-4 text-sm leading-relaxed text-toned">
|
|
@@ -476,8 +476,8 @@ onMounted(() => {
|
|
|
476
476
|
<template v-else>
|
|
477
477
|
<UInput
|
|
478
478
|
v-model="query"
|
|
479
|
-
|
|
480
|
-
:
|
|
479
|
+
icon="i-lucide-search"
|
|
480
|
+
:loading="searching"
|
|
481
481
|
size="sm"
|
|
482
482
|
class="w-full"
|
|
483
483
|
:placeholder="
|