@cat-factory/app 0.308.0 → 0.310.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/README.md +32 -2
  2. package/app/assets/css/main.css +22 -0
  3. package/app/assets/css/prose.css +4 -4
  4. package/app/assets/css/type.css +24 -0
  5. package/app/components/assistant/AssistantModal.vue +8 -6
  6. package/app/components/auth/BackendMisconfiguredScreen.vue +4 -3
  7. package/app/components/auth/LoginScreen.vue +40 -26
  8. package/app/components/auth/ResetPasswordScreen.vue +9 -4
  9. package/app/components/auth/UserMenu.vue +5 -4
  10. package/app/components/binaryCandidates/BinaryCandidatesWindow.vue +48 -37
  11. package/app/components/binaryOutput/BinaryOutputReport.vue +10 -14
  12. package/app/components/binaryOutput/StoredAssetView.vue +13 -11
  13. package/app/components/board/AddTaskModal.vue +30 -27
  14. package/app/components/board/AgentFailureCard.vue +21 -18
  15. package/app/components/board/AgentFailureHistory.vue +18 -9
  16. package/app/components/board/CreateInitiativeModal.vue +7 -6
  17. package/app/components/board/FailureDetail.vue +22 -12
  18. package/app/components/board/FailureHistoryList.vue +5 -5
  19. package/app/components/board/RecurringPipelineModal.vue +12 -11
  20. package/app/components/board/ReviewFrictionDialog.vue +8 -6
  21. package/app/components/board/StepExecutionHistory.vue +8 -8
  22. package/app/components/board/nodes/AgentChip.vue +1 -1
  23. package/app/components/board/nodes/BlockNode.vue +25 -24
  24. package/app/components/board/nodes/DecisionBadge.vue +4 -3
  25. package/app/components/board/nodes/EpicNode.vue +4 -4
  26. package/app/components/board/nodes/FrameSwimlanes.vue +8 -10
  27. package/app/components/board/nodes/InitiativeCard.vue +4 -4
  28. package/app/components/board/nodes/LaneGroup.vue +3 -5
  29. package/app/components/board/nodes/TaskCard.vue +20 -15
  30. package/app/components/board/nodes/TaskLane.vue +3 -3
  31. package/app/components/board/nodes/TaskPipelineMini.vue +21 -18
  32. package/app/components/bootstrap/AdoptionReviewModal.vue +36 -26
  33. package/app/components/bootstrap/BootstrapModal.vue +14 -13
  34. package/app/components/bootstrap/BootstrapRunSteps.vue +1 -1
  35. package/app/components/brainstorm/BrainstormWindow.vue +13 -10
  36. package/app/components/bugFishing/BugFishingWindow.vue +81 -58
  37. package/app/components/clarity/ClarityReviewWindow.vue +13 -10
  38. package/app/components/common/AsyncViewError.vue +1 -1
  39. package/app/components/common/ClarificationItem.vue +6 -6
  40. package/app/components/common/CopyButton.vue +12 -11
  41. package/app/components/common/DescriptorFields.vue +1 -1
  42. package/app/components/common/EmptyState.vue +1 -1
  43. package/app/components/common/IconButton.vue +34 -9
  44. package/app/components/common/InterviewGateNotice.vue +4 -4
  45. package/app/components/common/MarkdownProse.vue +8 -2
  46. package/app/components/common/SectionLabel.vue +38 -0
  47. package/app/components/consensus/ConsensusSessionWindow.vue +10 -9
  48. package/app/components/context/ContextAttachmentFields.vue +32 -20
  49. package/app/components/docs/DocInterviewWindow.vue +10 -9
  50. package/app/components/documents/ContextDocumentPicker.vue +22 -21
  51. package/app/components/documents/DocumentImportModal.vue +7 -5
  52. package/app/components/documents/DocumentOriginLink.vue +12 -9
  53. package/app/components/documents/DocumentSourceConnectModal.vue +2 -2
  54. package/app/components/documents/DocumentSyncState.vue +4 -4
  55. package/app/components/documents/DocumentTemplatesModal.vue +5 -4
  56. package/app/components/documents/RepoContextDocPicker.vue +7 -9
  57. package/app/components/documents/StartFromDesignModal.vue +4 -4
  58. package/app/components/documents/TaskContextDocs.vue +1 -1
  59. package/app/components/environments/EnvSetupStepper.vue +3 -3
  60. package/app/components/environments/EnvironmentStatusPanel.vue +15 -13
  61. package/app/components/environments/steps/EnvPreflightStep.vue +8 -8
  62. package/app/components/environments/steps/EnvReviewStep.vue +23 -22
  63. package/app/components/environments/steps/EnvSaveStep.vue +5 -5
  64. package/app/components/focus/BlockFocusView.vue +11 -10
  65. package/app/components/followUp/FollowUpWindow.vue +18 -15
  66. package/app/components/forkDecision/ForkDecisionWindow.vue +85 -97
  67. package/app/components/foundational/FoundationalContractSummary.vue +2 -2
  68. package/app/components/foundational/FoundationalServiceCatalogList.vue +6 -5
  69. package/app/components/foundational/FoundationalServiceRegistry.vue +10 -9
  70. package/app/components/foundational/FoundationalServiceSources.vue +7 -6
  71. package/app/components/foundational/FoundationalSuppressions.vue +2 -2
  72. package/app/components/fragments/FragmentLibraryManager.vue +8 -7
  73. package/app/components/fragments/FragmentSelector.vue +22 -20
  74. package/app/components/gates/GateFailingCheckList.vue +7 -6
  75. package/app/components/gates/GateResultView.vue +55 -49
  76. package/app/components/github/AddServiceFromRepoModal.vue +16 -11
  77. package/app/components/github/BranchProtectionPreflight.vue +5 -5
  78. package/app/components/github/GitHubConnect.vue +5 -3
  79. package/app/components/github/GitHubOnboarding.vue +7 -2
  80. package/app/components/github/GitHubPanel.vue +23 -19
  81. package/app/components/github/RepoTreeBrowser.vue +16 -13
  82. package/app/components/humanTest/HumanTestWindow.vue +29 -24
  83. package/app/components/initiative/InitiativePlanNotice.vue +3 -3
  84. package/app/components/initiative/InitiativePlanReview.vue +46 -38
  85. package/app/components/initiative/InitiativePlanningWindow.vue +5 -5
  86. package/app/components/initiative/InitiativeTrackerWindow.vue +213 -165
  87. package/app/components/judge/JudgeResultView.vue +28 -27
  88. package/app/components/kaizen/KaizenPanel.vue +70 -56
  89. package/app/components/kaizen/KaizenStepStatus.vue +7 -6
  90. package/app/components/layout/AccountAuditLog.vue +1 -1
  91. package/app/components/layout/AccountDeploymentSettings.vue +10 -10
  92. package/app/components/layout/AccountFailureKindRules.vue +13 -13
  93. package/app/components/layout/AccountFoundationalSettings.vue +1 -1
  94. package/app/components/layout/AccountFragmentSettings.vue +1 -1
  95. package/app/components/layout/AccountModelPolicySettings.vue +7 -7
  96. package/app/components/layout/AccountPlatformAlertSettings.vue +8 -8
  97. package/app/components/layout/AccountRiskPolicySettings.vue +2 -2
  98. package/app/components/layout/AccountRunCredentialSettings.vue +3 -3
  99. package/app/components/layout/AccountSkillSettings.vue +1 -1
  100. package/app/components/layout/AccountTeamSettings.vue +13 -9
  101. package/app/components/layout/AiProvidersBanner.vue +1 -1
  102. package/app/components/layout/AppearanceSwitcher.vue +5 -4
  103. package/app/components/layout/BoardSwitcher.vue +13 -10
  104. package/app/components/layout/BoardToolbar.vue +10 -5
  105. package/app/components/layout/CommandBar.vue +8 -5
  106. package/app/components/layout/DefaultTestEnvBanner.vue +6 -5
  107. package/app/components/layout/IntegrationsHub.vue +12 -9
  108. package/app/components/layout/LanguageSwitcher.vue +5 -4
  109. package/app/components/layout/ModelProvidersHub.vue +10 -7
  110. package/app/components/layout/NotificationsInbox.vue +47 -36
  111. package/app/components/layout/PersonalSetupModal.vue +9 -6
  112. package/app/components/layout/RolePrompt.vue +5 -4
  113. package/app/components/layout/SideBar.vue +7 -8
  114. package/app/components/layout/SpendWarningBanner.vue +3 -3
  115. package/app/components/layout/TranslationWarningBanner.vue +5 -4
  116. package/app/components/layout/UiModeSwitcher.vue +15 -12
  117. package/app/components/layout/UiRoleSwitcher.vue +10 -8
  118. package/app/components/layout/WorkspaceMembersSettings.vue +2 -2
  119. package/app/components/media/ArtifactLightbox.vue +74 -44
  120. package/app/components/media/ImageCompare.vue +80 -85
  121. package/app/components/merge/MergeEffortChips.vue +4 -6
  122. package/app/components/notifications/NotificationSettingsPanel.vue +6 -5
  123. package/app/components/observability/OutcomeFilterChips.vue +6 -5
  124. package/app/components/observability/RunFailureSummary.vue +22 -23
  125. package/app/components/observability/StepMetricsBar.vue +9 -8
  126. package/app/components/observability/StepModelActivity.vue +8 -5
  127. package/app/components/observability/ToolCallList.vue +19 -20
  128. package/app/components/outcome/OutcomeSummaryWindow.vue +48 -46
  129. package/app/components/palettes/AgentPalette.vue +21 -15
  130. package/app/components/palettes/AgentTierSelect.vue +1 -1
  131. package/app/components/palettes/PipelinePurposeSelect.vue +1 -1
  132. package/app/components/panels/AgentStepDetail.vue +57 -49
  133. package/app/components/panels/AttemptEntryHeader.vue +2 -2
  134. package/app/components/panels/DecisionModal.vue +1 -1
  135. package/app/components/panels/GenericStructuredResultView.vue +6 -5
  136. package/app/components/panels/InspectorPanel.vue +15 -13
  137. package/app/components/panels/MergerResultView.vue +10 -9
  138. package/app/components/panels/ObservabilityPanel.vue +211 -186
  139. package/app/components/panels/OperatorDashboardPanel.vue +124 -115
  140. package/app/components/panels/ReportsPanel.vue +87 -83
  141. package/app/components/panels/ReportsSpendBreakdown.vue +2 -2
  142. package/app/components/panels/ResultWindowShell.vue +152 -137
  143. package/app/components/panels/RunDetailLoadState.vue +1 -1
  144. package/app/components/panels/StepContainerStatus.vue +17 -16
  145. package/app/components/panels/StepDelegatedStatus.vue +24 -21
  146. package/app/components/panels/StepEffortReport.vue +12 -17
  147. package/app/components/panels/StepFragmentAdherence.vue +9 -10
  148. package/app/components/panels/StepMetadataCard.vue +41 -43
  149. package/app/components/panels/StepReproductionReport.vue +18 -20
  150. package/app/components/panels/StepRestartControl.vue +3 -2
  151. package/app/components/panels/StepRunMeta.vue +27 -26
  152. package/app/components/panels/StepTestReport.vue +16 -15
  153. package/app/components/panels/StepToolServers.vue +7 -8
  154. package/app/components/panels/StepValidationReport.vue +5 -5
  155. package/app/components/panels/inspector/ContainerSummary.vue +4 -4
  156. package/app/components/panels/inspector/DocReferenceRepos.vue +5 -4
  157. package/app/components/panels/inspector/EnvProbeReport.vue +16 -13
  158. package/app/components/panels/inspector/EpicChildren.vue +12 -10
  159. package/app/components/panels/inspector/FrontendBindingsResolved.vue +5 -4
  160. package/app/components/panels/inspector/FrontendConfig.vue +29 -36
  161. package/app/components/panels/inspector/InitiativeInspector.vue +3 -3
  162. package/app/components/panels/inspector/InspectorSection.vue +25 -17
  163. package/app/components/panels/inspector/RecurringScheduleSettings.vue +9 -8
  164. package/app/components/panels/inspector/ServiceConnections.vue +5 -4
  165. package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +2 -2
  166. package/app/components/panels/inspector/ServiceSelfTests.vue +9 -13
  167. package/app/components/panels/inspector/ServiceTestConfig.vue +46 -49
  168. package/app/components/panels/inspector/ServiceTestSecrets.vue +3 -3
  169. package/app/components/panels/inspector/ServiceTestingContext.vue +2 -2
  170. package/app/components/panels/inspector/ServiceValidationConfig.vue +2 -2
  171. package/app/components/panels/inspector/TaskAgentConfig.vue +2 -2
  172. package/app/components/panels/inspector/TaskAprioriBranches.vue +6 -5
  173. package/app/components/panels/inspector/TaskDependencies.vue +1 -1
  174. package/app/components/panels/inspector/TaskEstimateBadge.vue +3 -7
  175. package/app/components/panels/inspector/TaskExecution.vue +18 -16
  176. package/app/components/panels/inspector/TaskRunSettings.vue +45 -44
  177. package/app/components/panels/inspector/TaskStructure.vue +5 -4
  178. package/app/components/pipeline/AgentPromptEditor.vue +15 -14
  179. package/app/components/pipeline/BinaryOutputStepPicker.vue +42 -52
  180. package/app/components/pipeline/EstimateThresholdFields.vue +14 -11
  181. package/app/components/pipeline/GateConfigFields.vue +19 -23
  182. package/app/components/pipeline/IterationCapPrompt.vue +1 -1
  183. package/app/components/pipeline/PipelineBuilder.vue +113 -99
  184. package/app/components/pipeline/PipelineHealthModal.vue +7 -7
  185. package/app/components/pipeline/PipelinePicker.vue +11 -9
  186. package/app/components/pipeline/PipelinePreview.vue +6 -7
  187. package/app/components/pipeline/PipelineProgress.vue +49 -45
  188. package/app/components/prReview/PrReviewWindow.vue +87 -65
  189. package/app/components/providers/AiPresetMismatchDialog.vue +4 -3
  190. package/app/components/providers/AiProviderOnboardingModal.vue +2 -2
  191. package/app/components/providers/ApiKeysSection.vue +11 -9
  192. package/app/components/providers/PersonalSubscriptionSection.vue +4 -3
  193. package/app/components/providers/VendorCredentialsModal.vue +5 -4
  194. package/app/components/provisioning/ProvisioningLogsDrawer.vue +10 -9
  195. package/app/components/ralph/RalphLoopResultView.vue +29 -29
  196. package/app/components/requirements/RequirementsReviewWindow.vue +22 -17
  197. package/app/components/riskPolicy/RiskPolicyPicker.vue +13 -11
  198. package/app/components/riskPolicy/RiskPolicyPreview.vue +16 -15
  199. package/app/components/sandbox/SandboxPanel.vue +93 -73
  200. package/app/components/settings/ApiTokensPanel.vue +1 -1
  201. package/app/components/settings/BudgetSettings.vue +18 -17
  202. package/app/components/settings/CapabilityCredentialsPanel.vue +5 -5
  203. package/app/components/settings/CloudflareHandlerSection.vue +44 -41
  204. package/app/components/settings/ComposeEnvironmentSetupSection.vue +14 -12
  205. package/app/components/settings/ConnectionTestVerdict.vue +1 -1
  206. package/app/components/settings/ConsensusGroupsSection.vue +58 -47
  207. package/app/components/settings/CustomManifestTypeEditor.vue +8 -7
  208. package/app/components/settings/DefaultProvisionTypeSection.vue +5 -5
  209. package/app/components/settings/InfraHandlersConfigurator.vue +26 -24
  210. package/app/components/settings/InfrastructureBackendPicker.vue +18 -18
  211. package/app/components/settings/IssueTrackerPanel.vue +30 -21
  212. package/app/components/settings/KubernetesEngineForm.vue +9 -8
  213. package/app/components/settings/KubernetesEnvironmentForm.vue +4 -3
  214. package/app/components/settings/LocalContainerPoolSettings.vue +2 -2
  215. package/app/components/settings/LocalModelEndpointsPanel.vue +13 -11
  216. package/app/components/settings/MergeClassRulesEditor.vue +6 -5
  217. package/app/components/settings/MergeRolePolicyEditor.vue +13 -12
  218. package/app/components/settings/ModelConfigurationPanel.vue +17 -19
  219. package/app/components/settings/ModelPresetHealthModal.vue +3 -3
  220. package/app/components/settings/ObservabilityConnectionPanel.vue +1 -1
  221. package/app/components/settings/OpenRouterCatalogPanel.vue +10 -8
  222. package/app/components/settings/PackageRegistriesPanel.vue +4 -4
  223. package/app/components/settings/ProviderConnectionTab.vue +5 -4
  224. package/app/components/settings/ProviderManifestEditor.vue +9 -8
  225. package/app/components/settings/ProviderPreferenceEditor.vue +13 -13
  226. package/app/components/settings/RiskPolicyCreateForm.vue +40 -26
  227. package/app/components/settings/RiskPolicyEditorRow.vue +21 -19
  228. package/app/components/settings/RiskPolicyHealthModal.vue +3 -3
  229. package/app/components/settings/RiskPolicyPanel.vue +11 -13
  230. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +15 -12
  231. package/app/components/settings/SharedStacksPanel.vue +5 -5
  232. package/app/components/settings/TaskSourceCard.vue +2 -2
  233. package/app/components/settings/TaskTypeSuppressionsPanel.vue +4 -4
  234. package/app/components/settings/ToolServerChecklist.vue +22 -22
  235. package/app/components/settings/UsageSettings.vue +13 -13
  236. package/app/components/settings/UserSecretsSection.vue +4 -3
  237. package/app/components/settings/WorkspaceMetadataSettings.vue +6 -5
  238. package/app/components/settings/WorkspaceSettingsPanel.vue +72 -58
  239. package/app/components/skills/ReviewSkillQueue.vue +12 -10
  240. package/app/components/skills/SkillLibraryManager.vue +9 -8
  241. package/app/components/slack/SlackPanel.vue +17 -12
  242. package/app/components/spec/ServiceSpecWindow.vue +19 -19
  243. package/app/components/tasks/BugHuntModal.vue +1 -1
  244. package/app/components/tasks/ContextIssuePicker.vue +16 -15
  245. package/app/components/tasks/TaskContextIssues.vue +5 -4
  246. package/app/components/tasks/TaskImportModal.vue +3 -2
  247. package/app/components/tasks/TaskSourceConnectModal.vue +5 -5
  248. package/app/components/testing/TestReportWindow.vue +67 -60
  249. package/app/components/theme/ThemeImportModal.vue +2 -2
  250. package/app/components/tutorial/TutorialNudge.vue +3 -2
  251. package/app/components/tutorial/TutorialOverlay.vue +3 -2
  252. package/app/components/vcs/GitLabConnect.vue +1 -1
  253. package/app/components/visualConfirm/VisualConfirmationWindow.vue +79 -54
  254. package/app/utils/theme/builtins.ts +7 -1
  255. package/i18n/locales/de.json +7 -3
  256. package/i18n/locales/en.json +7 -3
  257. package/i18n/locales/es.json +7 -3
  258. package/i18n/locales/fr.json +7 -3
  259. package/i18n/locales/he.json +7 -3
  260. package/i18n/locales/it.json +7 -3
  261. package/i18n/locales/ja.json +7 -3
  262. package/i18n/locales/pl.json +7 -3
  263. package/i18n/locales/tr.json +7 -3
  264. package/i18n/locales/uk.json +7 -3
  265. package/package.json +1 -1
@@ -13,6 +13,8 @@
13
13
  // is a five-minute flow that touches a repo and can trial-provision a stack, so "when you need
14
14
  // this / when you don't" has to be answerable BEFORE opening it — most services on most boards
15
15
  // never need it at all.
16
+ import SectionLabel from '~/components/common/SectionLabel.vue'
17
+
16
18
  const { t } = useI18n()
17
19
  const ui = useUiStore()
18
20
 
@@ -52,34 +54,34 @@ function start() {
52
54
  </p>
53
55
  </div>
54
56
 
55
- <div class="rounded border border-default bg-default/40 p-3">
56
- <p class="text-[11px] font-medium uppercase tracking-wide text-muted">
57
+ <div class="rounded-sm border border-default bg-default/40 p-3">
58
+ <SectionLabel as="p">
57
59
  {{ t('settings.composeEnvSetup.how.title') }}
58
- </p>
59
- <ol class="mt-2 list-decimal space-y-1 ps-4 text-[11px] leading-relaxed text-muted">
60
+ </SectionLabel>
61
+ <ol class="mt-2 list-decimal space-y-1 ps-4 text-2xs leading-relaxed text-muted">
60
62
  <li v-for="(step, i) in steps" :key="`how-${i}`">{{ step }}</li>
61
63
  </ol>
62
- <p class="mt-2 text-[11px] leading-relaxed text-dimmed">
64
+ <p class="mt-2 text-2xs leading-relaxed text-dimmed">
63
65
  {{ t('settings.composeEnvSetup.how.outcome') }}
64
66
  </p>
65
67
  </div>
66
68
 
67
69
  <div class="grid gap-2 sm:grid-cols-2">
68
- <div class="rounded border border-app-success-900/50 bg-app-success-950/20 p-3">
69
- <p class="flex items-center gap-1.5 text-[11px] font-medium text-app-success-200/90">
70
+ <div class="rounded-sm border border-app-success-900/50 bg-app-success-950/20 p-3">
71
+ <p class="flex items-center gap-1.5 text-2xs font-medium text-app-success-200/90">
70
72
  <UIcon name="i-lucide-check" class="h-3.5 w-3.5 shrink-0" />
71
73
  {{ t('settings.composeEnvSetup.needed.title') }}
72
74
  </p>
73
- <ul class="mt-1.5 list-disc space-y-1 ps-4 text-[11px] leading-relaxed text-muted">
75
+ <ul class="mt-1.5 list-disc space-y-1 ps-4 text-2xs leading-relaxed text-muted">
74
76
  <li v-for="(item, i) in needed" :key="`need-${i}`">{{ item }}</li>
75
77
  </ul>
76
78
  </div>
77
- <div class="rounded border border-default bg-default/40 p-3">
78
- <p class="flex items-center gap-1.5 text-[11px] font-medium text-toned">
79
+ <div class="rounded-sm border border-default bg-default/40 p-3">
80
+ <p class="flex items-center gap-1.5 text-2xs font-medium text-toned">
79
81
  <UIcon name="i-lucide-minus" class="h-3.5 w-3.5 shrink-0" />
80
82
  {{ t('settings.composeEnvSetup.notNeeded.title') }}
81
83
  </p>
82
- <ul class="mt-1.5 list-disc space-y-1 ps-4 text-[11px] leading-relaxed text-muted">
84
+ <ul class="mt-1.5 list-disc space-y-1 ps-4 text-2xs leading-relaxed text-muted">
83
85
  <li v-for="(item, i) in notNeeded" :key="`skip-${i}`">{{ item }}</li>
84
86
  </ul>
85
87
  </div>
@@ -96,7 +98,7 @@ function start() {
96
98
  >
97
99
  {{ t('settings.composeEnvSetup.start') }}
98
100
  </UButton>
99
- <span class="text-[11px] text-dimmed">{{ t('settings.composeEnvSetup.rerunHint') }}</span>
101
+ <span class="text-2xs text-dimmed">{{ t('settings.composeEnvSetup.rerunHint') }}</span>
100
102
  </div>
101
103
  </section>
102
104
  </template>
@@ -57,6 +57,6 @@ const detail = computed(() => (causeKey.value ? (props.result?.message ?? '') :
57
57
  </p>
58
58
  <div v-else-if="result" class="space-y-0.5" data-testid="connection-test-result">
59
59
  <p class="text-xs text-app-error-400 break-words">{{ headline }}</p>
60
- <p v-if="detail" class="text-[11px] text-muted break-words">{{ detail }}</p>
60
+ <p v-if="detail" class="text-2xs text-muted break-words">{{ detail }}</p>
61
61
  </div>
62
62
  </template>
@@ -13,6 +13,8 @@ import { computed, ref } from 'vue'
13
13
  import type { ConsensusGroup, ConsensusStrategy } from '~/types/consensus'
14
14
  import { isSelectable } from '~/stores/models'
15
15
  import { uid } from '~/utils/catalog'
16
+ import SectionLabel from '~/components/common/SectionLabel.vue'
17
+ import IconButton from '~/components/common/IconButton.vue'
16
18
 
17
19
  const { t } = useI18n()
18
20
  const groups = useConsensusGroupsStore()
@@ -115,6 +117,17 @@ const selectableModelIds = computed(() => {
115
117
  .map((m) => ({ id: m.id, label: m.label }))
116
118
  })
117
119
 
120
+ // "Inherit the step's model" is a NAMED sentinel in the picker, never an empty string and never
121
+ // `undefined`: a Select reserves both for "nothing selected", so an item carrying one either
122
+ // throws (empty string) or renders the placeholder over its own label (undefined). One item list
123
+ // serves both pickers; each binding below maps the sentinel to what its field holds on the wire,
124
+ // which is `undefined` for a participant and an empty string for the synthesizer.
125
+ const STEP_MODEL = '__step__'
126
+ const modelItems = computed(() => [
127
+ { label: t('settings.consensusGroups.editor.stepModel'), value: STEP_MODEL },
128
+ ...selectableModelIds.value.map((m) => ({ label: m.label, value: m.id })),
129
+ ])
130
+
118
131
  /**
119
132
  * A threshold field's value as the contract wants it, or undefined when the author left it blank.
120
133
  *
@@ -245,27 +258,27 @@ async function remove(group: ConsensusGroup) {
245
258
  <span class="truncate text-sm font-semibold text-app-100">{{ g.name }}</span>
246
259
  <UBadge color="neutral" variant="subtle" size="xs">{{ barLabel(g) }}</UBadge>
247
260
  <div class="ms-auto flex items-center gap-1">
248
- <UButton
261
+ <IconButton
249
262
  size="xs"
250
263
  variant="ghost"
251
264
  color="neutral"
252
265
  icon="i-lucide-pencil"
253
- :title="t('settings.consensusGroups.list.editTitle')"
266
+ :label="t('settings.consensusGroups.list.editTitle')"
254
267
  @click="startEdit(g)"
255
268
  />
256
- <UButton
269
+ <IconButton
257
270
  size="xs"
258
271
  variant="ghost"
259
272
  color="error"
260
273
  icon="i-lucide-trash-2"
261
274
  :loading="busy"
262
- :title="t('settings.consensusGroups.list.deleteTitle')"
275
+ :label="t('settings.consensusGroups.list.deleteTitle')"
263
276
  @click="remove(g)"
264
277
  />
265
278
  </div>
266
279
  </div>
267
- <p v-if="g.description" class="mt-1 text-[11px] text-muted">{{ g.description }}</p>
268
- <div class="mt-1.5 text-[11px] text-muted">
280
+ <p v-if="g.description" class="mt-1 text-2xs text-muted">{{ g.description }}</p>
281
+ <div class="mt-1.5 text-2xs text-muted">
269
282
  {{ t(`pipeline.builder.strategyOption.${g.strategy}`) }}
270
283
  ·
271
284
  {{
@@ -287,9 +300,9 @@ async function remove(group: ConsensusGroup) {
287
300
  <div v-else class="space-y-4 rounded-xl border border-default bg-default/50 p-4">
288
301
  <div class="grid gap-3 sm:grid-cols-2">
289
302
  <div>
290
- <label class="mb-1 block text-[11px] font-semibold uppercase tracking-wide text-muted">
303
+ <SectionLabel as="label" class="mb-1 block">
291
304
  {{ t('settings.consensusGroups.editor.nameLabel') }}
292
- </label>
305
+ </SectionLabel>
293
306
  <UInput
294
307
  v-model="editor.name"
295
308
  size="sm"
@@ -298,22 +311,17 @@ async function remove(group: ConsensusGroup) {
298
311
  />
299
312
  </div>
300
313
  <div>
301
- <label class="mb-1 block text-[11px] font-semibold uppercase tracking-wide text-muted">
314
+ <SectionLabel as="label" class="mb-1 block">
302
315
  {{ t('settings.consensusGroups.editor.strategyLabel') }}
303
- </label>
304
- <select
305
- v-model="editor.strategy"
306
- class="w-full rounded border border-muted bg-default px-2 py-1.5 text-sm text-app-100"
307
- >
308
- <option v-for="s in STRATEGIES" :key="s.value" :value="s.value">{{ s.label }}</option>
309
- </select>
316
+ </SectionLabel>
317
+ <USelect v-model="editor.strategy" :items="STRATEGIES" size="sm" class="w-full" />
310
318
  </div>
311
319
  </div>
312
320
 
313
321
  <div>
314
- <label class="mb-1 block text-[11px] font-semibold uppercase tracking-wide text-muted">
322
+ <SectionLabel as="label" class="mb-1 block">
315
323
  {{ t('settings.consensusGroups.editor.descriptionLabel') }}
316
- </label>
324
+ </SectionLabel>
317
325
  <UInput
318
326
  v-model="editor.description"
319
327
  size="sm"
@@ -325,9 +333,9 @@ async function remove(group: ConsensusGroup) {
325
333
  <!-- participants -->
326
334
  <div class="space-y-2">
327
335
  <div class="flex items-center justify-between">
328
- <span class="text-[11px] font-semibold uppercase tracking-wide text-muted">
336
+ <SectionLabel as="span">
329
337
  {{ t('settings.consensusGroups.editor.participantsLabel') }}
330
- </span>
338
+ </SectionLabel>
331
339
  <UButton
332
340
  icon="i-lucide-plus"
333
341
  color="neutral"
@@ -337,7 +345,7 @@ async function remove(group: ConsensusGroup) {
337
345
  @click="addParticipant"
338
346
  />
339
347
  </div>
340
- <p class="text-[11px] text-dimmed">
348
+ <p class="text-2xs text-dimmed">
341
349
  {{ t('settings.consensusGroups.editor.participantsHint') }}
342
350
  </p>
343
351
  <div
@@ -357,20 +365,20 @@ async function remove(group: ConsensusGroup) {
357
365
  class="min-w-40 flex-1"
358
366
  :placeholder="t('settings.consensusGroups.editor.framingPlaceholder')"
359
367
  />
360
- <select
361
- v-model="p.modelId"
362
- class="w-44 rounded border border-muted bg-default px-1.5 py-1 text-xs text-toned"
363
- >
364
- <option :value="undefined">{{ t('settings.consensusGroups.editor.stepModel') }}</option>
365
- <option v-for="m in selectableModelIds" :key="m.id" :value="m.id">{{ m.label }}</option>
366
- </select>
367
- <UButton
368
+ <USelect
369
+ :model-value="p.modelId || STEP_MODEL"
370
+ :items="modelItems"
371
+ size="xs"
372
+ class="w-44"
373
+ @update:model-value="p.modelId = $event === STEP_MODEL ? undefined : String($event)"
374
+ />
375
+ <IconButton
368
376
  icon="i-lucide-x"
369
377
  color="error"
370
378
  variant="ghost"
371
379
  size="xs"
372
380
  :disabled="editor.participants.length <= 2"
373
- :title="t('pipeline.builder.removeParticipant')"
381
+ :label="t('pipeline.builder.removeParticipant')"
374
382
  @click="removeParticipant(index)"
375
383
  />
376
384
  </div>
@@ -378,32 +386,35 @@ async function remove(group: ConsensusGroup) {
378
386
 
379
387
  <div class="grid gap-3 sm:grid-cols-2">
380
388
  <div>
381
- <label class="mb-1 block text-[11px] font-semibold uppercase tracking-wide text-muted">
389
+ <SectionLabel as="label" class="mb-1 block">
382
390
  {{ t('settings.consensusGroups.editor.synthesizerLabel') }}
383
- </label>
384
- <select
385
- v-model="editor.synthesizerModelId"
386
- class="w-full rounded border border-muted bg-default px-2 py-1.5 text-sm text-app-100"
387
- >
388
- <option value="">{{ t('settings.consensusGroups.editor.stepModel') }}</option>
389
- <option v-for="m in selectableModelIds" :key="m.id" :value="m.id">{{ m.label }}</option>
390
- </select>
391
+ </SectionLabel>
392
+ <USelect
393
+ :model-value="editor.synthesizerModelId || STEP_MODEL"
394
+ :items="modelItems"
395
+ size="sm"
396
+ class="w-full"
397
+ @update:model-value="
398
+ editor.synthesizerModelId = $event === STEP_MODEL ? '' : String($event)
399
+ "
400
+ />
391
401
  </div>
392
402
  <div v-if="editor.strategy === 'debate'">
393
- <label class="mb-1 block text-[11px] font-semibold uppercase tracking-wide text-muted">
403
+ <SectionLabel as="label" class="mb-1 block">
394
404
  {{ t('pipeline.builder.rounds') }}
395
- </label>
405
+ </SectionLabel>
396
406
  <UInput v-model.number="editor.rounds" type="number" min="1" max="5" size="sm" />
397
407
  </div>
398
408
  </div>
399
409
 
400
410
  <!-- the estimate bar -->
401
411
  <div class="space-y-2 rounded-lg border border-default bg-app-950/40 p-3">
402
- <label class="flex items-center gap-2 text-xs text-toned">
403
- <input v-model="editor.gated" type="checkbox" class="accent-app-success-500" />
404
- {{ t('settings.consensusGroups.editor.gatedLabel') }}
405
- </label>
406
- <p class="text-[11px] text-dimmed">
412
+ <UCheckbox
413
+ v-model="editor.gated"
414
+ size="xs"
415
+ :label="t('settings.consensusGroups.editor.gatedLabel')"
416
+ />
417
+ <p class="text-2xs text-dimmed">
407
418
  {{ t('settings.consensusGroups.editor.gatedHint') }}
408
419
  </p>
409
420
  <div v-if="editor.gated" class="flex flex-wrap items-center gap-3 text-xs">
@@ -453,7 +464,7 @@ async function remove(group: ConsensusGroup) {
453
464
  />
454
465
  </label>
455
466
  </div>
456
- <p v-if="gatingIncomplete" class="text-[11px] text-app-warning-400">
467
+ <p v-if="gatingIncomplete" class="text-2xs text-app-warning-400">
457
468
  {{ t('settings.consensusGroups.editor.gatingIncomplete') }}
458
469
  </p>
459
470
  </div>
@@ -6,6 +6,7 @@
6
6
  // infraConfig store (`PUT|DELETE /environments/custom-types/:manifestId`).
7
7
  import { computed, reactive, ref } from 'vue'
8
8
  import type { CustomManifestType } from '@cat-factory/contracts'
9
+ import SectionLabel from '~/components/common/SectionLabel.vue'
9
10
 
10
11
  const { t } = useI18n()
11
12
  const infra = useInfraConfigStore()
@@ -95,7 +96,7 @@ async function remove(type: CustomManifestType) {
95
96
  <h3 class="text-sm font-semibold text-default">
96
97
  {{ t('settings.infrastructure.customType.title') }}
97
98
  </h3>
98
- <p class="text-[11px] text-dimmed">{{ t('settings.infrastructure.customType.hint') }}</p>
99
+ <p class="text-2xs text-dimmed">{{ t('settings.infrastructure.customType.hint') }}</p>
99
100
  </div>
100
101
 
101
102
  <!-- The catalog: registered (read-only) + workspace (editable). -->
@@ -107,7 +108,7 @@ async function remove(type: CustomManifestType) {
107
108
  >
108
109
  <div class="min-w-0">
109
110
  <div class="flex items-center gap-1.5">
110
- <span class="truncate text-[13px] text-default">{{ type.label }}</span>
111
+ <span class="truncate text-sm text-default">{{ type.label }}</span>
111
112
  <UBadge
112
113
  :color="type.source === 'workspace' ? 'primary' : 'neutral'"
113
114
  variant="subtle"
@@ -116,8 +117,8 @@ async function remove(type: CustomManifestType) {
116
117
  {{ t(`settings.infrastructure.customType.source.${type.source}`) }}
117
118
  </UBadge>
118
119
  </div>
119
- <code class="text-[11px] text-dimmed">{{ type.manifestId }}</code>
120
- <p v-if="type.description" class="text-[11px] text-muted">{{ type.description }}</p>
120
+ <code class="text-2xs text-dimmed">{{ type.manifestId }}</code>
121
+ <p v-if="type.description" class="text-2xs text-muted">{{ type.description }}</p>
121
122
  </div>
122
123
  <div v-if="type.source === 'workspace'" class="flex shrink-0 items-center gap-0.5">
123
124
  <UButton
@@ -139,19 +140,19 @@ async function remove(type: CustomManifestType) {
139
140
  </div>
140
141
  </li>
141
142
  </ul>
142
- <p v-else class="text-[11px] text-dimmed">
143
+ <p v-else class="text-2xs text-dimmed">
143
144
  {{ t('settings.infrastructure.customType.empty') }}
144
145
  </p>
145
146
 
146
147
  <!-- Add / edit a workspace-defined type. -->
147
148
  <div class="space-y-2 border-t border-default pt-3">
148
- <p class="text-[11px] font-semibold uppercase tracking-wide text-muted">
149
+ <SectionLabel as="p">
149
150
  {{
150
151
  editing
151
152
  ? t('settings.infrastructure.customType.editTitle', { id: draft.manifestId })
152
153
  : t('settings.infrastructure.customType.addTitle')
153
154
  }}
154
- </p>
155
+ </SectionLabel>
155
156
  <UFormField
156
157
  v-if="!editing"
157
158
  :label="t('settings.infrastructure.customType.manifestId')"
@@ -105,7 +105,7 @@ async function save() {
105
105
  <h3 class="text-sm font-semibold text-default">
106
106
  {{ t('settings.defaultProvision.title') }}
107
107
  </h3>
108
- <p class="mt-1 text-[11px] leading-snug text-dimmed">
108
+ <p class="mt-1 text-2xs leading-snug text-dimmed">
109
109
  {{ t('settings.defaultProvision.hint') }}
110
110
  </p>
111
111
  </div>
@@ -125,7 +125,7 @@ async function save() {
125
125
  </div>
126
126
 
127
127
  <div v-if="selection.type === 'custom'" class="space-y-1">
128
- <label class="text-[11px] text-muted">
128
+ <label class="text-2xs text-muted">
129
129
  {{ t('inspector.testConfig.customManifestId') }}
130
130
  </label>
131
131
  <USelect
@@ -138,7 +138,7 @@ async function save() {
138
138
  :placeholder="t('inspector.testConfig.customManifestIdPlaceholder')"
139
139
  @update:model-value="(v: string) => setManifestId(v)"
140
140
  />
141
- <p v-else class="text-[11px] leading-snug text-app-warning-300/80">
141
+ <p v-else class="text-2xs leading-snug text-app-warning-300/80">
142
142
  {{ t('inspector.testConfig.customNoTypes') }}
143
143
  </p>
144
144
  </div>
@@ -147,7 +147,7 @@ async function save() {
147
147
  the workspace still has nothing stored. -->
148
148
  <p
149
149
  v-if="unset && selection.type"
150
- class="text-[11px] leading-snug text-app-warning-300/80"
150
+ class="text-2xs leading-snug text-app-warning-300/80"
151
151
  data-testid="default-provision-suggestion-hint"
152
152
  >
153
153
  {{ t('settings.defaultProvision.suggestionHint') }}
@@ -165,7 +165,7 @@ async function save() {
165
165
  >
166
166
  {{ t('settings.defaultProvision.save') }}
167
167
  </UButton>
168
- <span v-if="!unset && !dirty" class="text-[11px] text-dimmed">
168
+ <span v-if="!unset && !dirty" class="text-2xs text-dimmed">
169
169
  {{ t('settings.defaultProvision.savedState') }}
170
170
  </span>
171
171
  </div>
@@ -30,6 +30,7 @@ import CustomManifestTypeEditor from '~/components/settings/CustomManifestTypeEd
30
30
  import CloudflareHandlerSection from '~/components/settings/CloudflareHandlerSection.vue'
31
31
  import ConnectionTestVerdict from '~/components/settings/ConnectionTestVerdict.vue'
32
32
  import { consumeKubernetesScrollAnchor } from '~/components/settings/InfraHandlersConfigurator.logic'
33
+ import SectionLabel from '~/components/common/SectionLabel.vue'
33
34
 
34
35
  const { t } = useI18n()
35
36
  const infra = useInfraConfigStore()
@@ -442,7 +443,7 @@ function toastRemoved() {
442
443
  disabled
443
444
  size="lg"
444
445
  :label="t('settings.infrastructure.handler.connectionEstablished')"
445
- :ui="{ label: 'text-[13px] font-semibold text-app-success-300' }"
446
+ :ui="{ label: 'text-sm font-semibold text-app-success-300' }"
446
447
  />
447
448
  <UButton
448
449
  icon="i-lucide-trash-2"
@@ -454,7 +455,7 @@ function toastRemoved() {
454
455
  @click="removeKube"
455
456
  />
456
457
  </div>
457
- <p class="pl-7 text-[11px] text-toned">
458
+ <p class="pl-7 text-2xs text-toned">
458
459
  {{ t('settings.infrastructure.handler.activeEngine') }}
459
460
  <span class="text-default">{{ kubeHandlerEngineLabel }}</span>
460
461
  </p>
@@ -472,13 +473,13 @@ function toastRemoved() {
472
473
  <ConnectionTestVerdict :result="kubeSavedTestResult" />
473
474
  </div>
474
475
  </div>
475
- <p v-else class="flex items-center gap-1.5 text-[12px] text-dimmed">
476
+ <p v-else class="flex items-center gap-1.5 text-xs text-dimmed">
476
477
  <UIcon name="i-lucide-circle-dashed" class="h-3.5 w-3.5" />
477
478
  {{ t('settings.infrastructure.handler.notConnected') }}
478
479
  </p>
479
480
 
480
481
  <div class="space-y-1">
481
- <span class="text-[11px] text-muted">{{
482
+ <span class="text-2xs text-muted">{{
482
483
  t('settings.infrastructure.handler.engineLabel')
483
484
  }}</span>
484
485
  <div class="flex flex-wrap gap-1">
@@ -513,22 +514,26 @@ function toastRemoved() {
513
514
 
514
515
  <!-- Local mode: a personal override for THIS machine, layered over the workspace handler. -->
515
516
  <div v-if="userOverridesOn" class="border-t border-default pt-2">
516
- <button
517
- type="button"
518
- class="flex w-full items-center gap-1.5 text-start text-[11px] font-semibold uppercase tracking-wide text-dimmed hover:text-toned"
517
+ <UButton
518
+ color="neutral"
519
+ variant="link"
520
+ class="group w-full p-0"
521
+ :aria-expanded="showKubeOverride"
519
522
  @click="showKubeOverride = !showKubeOverride"
520
523
  >
521
- <UIcon
522
- :name="showKubeOverride ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
523
- class="h-3.5 w-3.5"
524
- />
525
- {{ t('settings.infrastructure.handler.personalOverride') }}
526
- <UBadge v-if="kubeUserHandler" color="primary" variant="subtle" size="sm">
527
- {{ t('settings.infrastructure.handler.overrideActive') }}
528
- </UBadge>
529
- </button>
524
+ <SectionLabel as="span" class="flex w-full items-center gap-1.5 group-hover:text-default">
525
+ <UIcon
526
+ :name="showKubeOverride ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
527
+ class="h-3.5 w-3.5"
528
+ />
529
+ {{ t('settings.infrastructure.handler.personalOverride') }}
530
+ <UBadge v-if="kubeUserHandler" color="primary" variant="subtle" size="sm">
531
+ {{ t('settings.infrastructure.handler.overrideActive') }}
532
+ </UBadge>
533
+ </SectionLabel>
534
+ </UButton>
530
535
  <div v-if="showKubeOverride" class="mt-2 space-y-2">
531
- <p class="text-[11px] text-dimmed">
536
+ <p class="text-2xs text-dimmed">
532
537
  {{ t('settings.infrastructure.handler.personalOverrideHint') }}
533
538
  </p>
534
539
  <p v-if="kubeUserHandler" class="flex justify-end">
@@ -562,7 +567,7 @@ function toastRemoved() {
562
567
  <h3 class="text-sm font-semibold text-default">
563
568
  {{ t('inspector.testConfig.provisionTypes.docker-compose') }}
564
569
  </h3>
565
- <p class="text-[12px] text-muted">
570
+ <p class="text-xs text-muted">
566
571
  {{ t('settings.infrastructure.dockerComposeInfo') }}
567
572
  </p>
568
573
  </section>
@@ -580,9 +585,9 @@ function toastRemoved() {
580
585
  <CustomManifestTypeEditor />
581
586
 
582
587
  <div v-if="infra.customTypes.length" class="space-y-2 border-t border-default pt-3">
583
- <p class="text-[11px] font-semibold uppercase tracking-wide text-muted">
588
+ <SectionLabel as="p">
584
589
  {{ t('settings.infrastructure.handler.customHandlerTitle') }}
585
- </p>
590
+ </SectionLabel>
586
591
  <UFormField :label="t('settings.infrastructure.handler.customTypeLabel')">
587
592
  <USelect v-model="selectedCustomId" :items="customTypeItems" />
588
593
  </UFormField>
@@ -595,10 +600,7 @@ function toastRemoved() {
595
600
  >
596
601
  <USelect v-model="selectedBackendKind" :items="customBackendOptions" />
597
602
  </UFormField>
598
- <p
599
- v-if="customHandler"
600
- class="flex items-center justify-between gap-2 text-[12px] text-toned"
601
- >
603
+ <p v-if="customHandler" class="flex items-center justify-between gap-2 text-xs text-toned">
602
604
  <span class="text-app-success-400">{{
603
605
  t('settings.infrastructure.handler.customConnected')
604
606
  }}</span>
@@ -199,6 +199,16 @@ const items = computed<PickerItem[]>(() => {
199
199
  return out
200
200
  })
201
201
 
202
+ // The same list in the shape URadioGroup reads. `desc` is optional on a PickerItem and an empty
203
+ // string renders an empty description line, so it is dropped rather than passed through blank.
204
+ const backendItems = computed(() =>
205
+ items.value.map((item) => ({
206
+ value: item.id,
207
+ label: item.label,
208
+ ...(item.desc ? { description: item.desc } : {}),
209
+ })),
210
+ )
211
+
202
212
  // The active-line label: prefer the registered connection's concrete kind over the generic
203
213
  // "delegated" label so "Active: Kubernetes cluster" reads truthfully.
204
214
  const activeLabel = computed(() => {
@@ -275,25 +285,15 @@ const labelKey = computed(() =>
275
285
  {{ t('settings.infrastructure.active', { backend: activeLabel }) }}
276
286
  </p>
277
287
 
278
- <div class="space-y-1.5" :data-testid="`${axis}-backend-options`">
279
- <label v-for="item in items" :key="item.id" class="flex cursor-pointer items-start gap-2">
280
- <input
281
- type="radio"
282
- class="mt-1"
283
- :value="item.id"
284
- :checked="item.id === selected"
285
- :disabled="saving"
286
- :data-testid="`${axis}-backend-${item.id}`"
287
- @change="select(item.id)"
288
- />
289
- <span class="min-w-0">
290
- <span class="text-sm text-default">{{ item.label }}</span>
291
- <span v-if="item.desc" class="block text-[11px] text-muted">{{ item.desc }}</span>
292
- </span>
293
- </label>
294
- </div>
288
+ <URadioGroup
289
+ :model-value="selected"
290
+ :items="backendItems"
291
+ :disabled="saving"
292
+ :data-testid="`${axis}-backend-options`"
293
+ @update:model-value="select(String($event))"
294
+ />
295
295
 
296
- <p v-if="showRegisterHint" class="text-[11px] text-app-warning-300/80">
296
+ <p v-if="showRegisterHint" class="text-2xs text-app-warning-300/80">
297
297
  {{ t('settings.infrastructure.registerHint') }}
298
298
  </p>
299
299