@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
@@ -6,6 +6,7 @@
6
6
  // and Fixtures (the graded inputs each run is scored against). Loaded on demand when the
7
7
  // window opens; 503 (the deployment hasn't provisioned the Sandbox DB) shows a notice.
8
8
  import { computed, ref, watch } from 'vue'
9
+ import type { TableColumn } from '@nuxt/ui'
9
10
  import type {
10
11
  SandboxExperimentStatus,
11
12
  SandboxFixtureKind,
@@ -15,6 +16,8 @@ import type {
15
16
  SandboxRun,
16
17
  SandboxUnsupportedReason,
17
18
  } from '~/types/sandbox'
19
+ import SectionLabel from '~/components/common/SectionLabel.vue'
20
+ import IconButton from '~/components/common/IconButton.vue'
18
21
 
19
22
  const ui = useUiStore()
20
23
  const store = useSandboxStore()
@@ -221,6 +224,16 @@ const detailRows = computed(() =>
221
224
  )
222
225
  const selectedRun = ref<SandboxRun | null>(null)
223
226
 
227
+ // Column definitions rather than hand-written `<th>`s: the header typography then comes from the
228
+ // table theme instead of a per-file recipe.
229
+ const detailColumns = computed<TableColumn<(typeof detailRows.value)[number]>[]>(() => [
230
+ { id: 'prompt', header: t('sandbox.results.col.prompt') },
231
+ { id: 'model', header: t('sandbox.results.col.model') },
232
+ { id: 'fixture', header: t('sandbox.results.col.fixture') },
233
+ { id: 'score', header: t('sandbox.results.col.score') },
234
+ { id: 'objective', header: t('sandbox.results.col.objective') },
235
+ ])
236
+
224
237
  function scoreColor(score: number): string {
225
238
  if (score >= 4) return 'text-app-success-400'
226
239
  if (score >= 3) return 'text-app-warning-400'
@@ -317,9 +330,9 @@ async function archive(prompt: SandboxPromptVersion) {
317
330
  <div v-if="tab === 'experiments'" class="grid gap-4 lg:grid-cols-2">
318
331
  <!-- builder -->
319
332
  <div class="space-y-3 rounded-lg border border-muted bg-default/40 p-3">
320
- <p class="text-[11px] font-semibold uppercase tracking-wide text-muted">
333
+ <SectionLabel as="p">
321
334
  {{ t('sandbox.builder.title') }}
322
- </p>
335
+ </SectionLabel>
323
336
 
324
337
  <UFormField :label="t('sandbox.builder.agent')">
325
338
  <USelect
@@ -331,7 +344,7 @@ async function archive(prompt: SandboxPromptVersion) {
331
344
  <p
332
345
  v-for="excluded in unrunnableAgentKinds"
333
346
  :key="excluded.agentKind"
334
- class="mt-1 text-[11px] leading-snug text-dimmed"
347
+ class="mt-1 text-2xs leading-snug text-dimmed"
335
348
  >
336
349
  <span class="font-medium text-muted">{{ excluded.label }}:</span>
337
350
  {{
@@ -343,9 +356,9 @@ async function archive(prompt: SandboxPromptVersion) {
343
356
  </UFormField>
344
357
 
345
358
  <div>
346
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-dimmed">
359
+ <SectionLabel as="span" class="mb-1 block">
347
360
  {{ t('sandbox.builder.promptVersions') }}
348
- </span>
361
+ </SectionLabel>
349
362
  <div class="max-h-28 space-y-1 overflow-auto pe-1">
350
363
  <label
351
364
  v-for="p in kindPrompts"
@@ -375,9 +388,9 @@ async function archive(prompt: SandboxPromptVersion) {
375
388
  </div>
376
389
 
377
390
  <div>
378
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-dimmed">
391
+ <SectionLabel as="span" class="mb-1 block">
379
392
  {{ t('sandbox.builder.models') }}
380
- </span>
393
+ </SectionLabel>
381
394
  <div class="max-h-28 space-y-1 overflow-auto pe-1">
382
395
  <label
383
396
  v-for="m in store.selectableModels"
@@ -399,9 +412,9 @@ async function archive(prompt: SandboxPromptVersion) {
399
412
  </div>
400
413
 
401
414
  <div>
402
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-dimmed">
415
+ <SectionLabel as="span" class="mb-1 block">
403
416
  {{ t('sandbox.builder.fixtures') }}
404
- </span>
417
+ </SectionLabel>
405
418
  <div class="max-h-28 space-y-1 overflow-auto pe-1">
406
419
  <label
407
420
  v-for="f in kindFixtures"
@@ -468,74 +481,79 @@ async function archive(prompt: SandboxPromptVersion) {
468
481
  }}</UBadge>
469
482
  </div>
470
483
  <div class="overflow-auto">
471
- <table class="w-full text-start text-xs">
472
- <thead class="text-dimmed">
473
- <tr>
474
- <th class="py-1 pe-2 font-medium">{{ t('sandbox.results.col.prompt') }}</th>
475
- <th class="py-1 pe-2 font-medium">{{ t('sandbox.results.col.model') }}</th>
476
- <th class="py-1 pe-2 font-medium">{{ t('sandbox.results.col.fixture') }}</th>
477
- <th class="py-1 pe-2 font-medium">{{ t('sandbox.results.col.score') }}</th>
478
- <th class="py-1 font-medium">{{ t('sandbox.results.col.objective') }}</th>
479
- </tr>
480
- </thead>
481
- <tbody>
482
- <tr
483
- v-for="{ run, grade, fixtureName } in detailRows"
484
- :key="run.id"
485
- class="cursor-pointer border-t border-default hover:bg-elevated/40"
486
- @click="selectedRun = run"
484
+ <UTable
485
+ :data="detailRows"
486
+ :columns="detailColumns"
487
+ :ui="{
488
+ base: 'text-xs',
489
+ th: 'ps-0 pe-2 py-1',
490
+ td: 'ps-0 pe-2 py-1 text-xs whitespace-normal',
491
+ tbody: '[&>tr]:cursor-pointer',
492
+ empty: 'py-0',
493
+ }"
494
+ @select="(_event, row) => (selectedRun = row.original.run)"
495
+ >
496
+ <template #prompt-cell="{ row }">
497
+ <span class="text-toned">{{ row.original.run.promptLabel }}</span>
498
+ </template>
499
+ <template #model-cell="{ row }">
500
+ <span class="font-mono text-2xs text-muted">{{ row.original.run.model }}</span>
501
+ </template>
502
+ <template #fixture-cell="{ row }">
503
+ <span class="text-muted">{{ row.original.fixtureName }}</span>
504
+ </template>
505
+ <template #score-cell="{ row }">
506
+ <span
507
+ v-if="row.original.grade"
508
+ :class="scoreColor(row.original.grade.weightedTotal)"
509
+ class="font-semibold"
510
+ >
511
+ {{ row.original.grade.weightedTotal.toFixed(2) }}
512
+ </span>
513
+ <span
514
+ v-else-if="row.original.run.status === 'failed'"
515
+ class="text-app-error-400"
516
+ >{{ t('sandbox.results.failed') }}</span
517
+ >
518
+ <span v-else class="text-app-600">&mdash;</span>
519
+ </template>
520
+ <template #objective-cell="{ row }">
521
+ <span
522
+ v-if="row.original.grade?.objective"
523
+ :class="
524
+ row.original.grade.objective.pass
525
+ ? 'text-app-success-400'
526
+ : 'text-app-warning-400'
527
+ "
487
528
  >
488
- <td class="py-1 pe-2 text-toned">{{ run.promptLabel }}</td>
489
- <td class="py-1 pe-2 font-mono text-[11px] text-muted">
490
- {{ run.model }}
491
- </td>
492
- <td class="py-1 pe-2 text-muted">{{ fixtureName }}</td>
493
- <td class="py-1 pe-2">
494
- <span
495
- v-if="grade"
496
- :class="scoreColor(grade.weightedTotal)"
497
- class="font-semibold"
498
- >
499
- {{ grade.weightedTotal.toFixed(2) }}
500
- </span>
501
- <span v-else-if="run.status === 'failed'" class="text-app-error-400">{{
502
- t('sandbox.results.failed')
503
- }}</span>
504
- <span v-else class="text-app-600">—</span>
505
- </td>
506
- <td class="py-1">
507
- <span
508
- v-if="grade?.objective"
509
- :class="
510
- grade.objective.pass ? 'text-app-success-400' : 'text-app-warning-400'
511
- "
512
- >
513
- {{ grade.objective.caught }}/{{ grade.objective.total }}
514
- </span>
515
- <span v-else class="text-app-600">—</span>
516
- </td>
517
- </tr>
518
- </tbody>
519
- </table>
529
+ {{ row.original.grade.objective.caught }}/{{
530
+ row.original.grade.objective.total
531
+ }}
532
+ </span>
533
+ <span v-else class="text-app-600">&mdash;</span>
534
+ </template>
535
+ <!-- See InitiativeTrackerWindow: the fallback copy is untranslated. -->
536
+ <template #empty />
537
+ </UTable>
520
538
  </div>
521
539
 
522
540
  <!-- selected cell output -->
523
541
  <div v-if="selectedRun" class="mt-3 border-t border-default pt-2">
524
- <p class="mb-1 text-[11px] uppercase tracking-wide text-dimmed">
542
+ <SectionLabel as="p" class="mb-1">
525
543
  {{ selectedRun.promptLabel }} · {{ selectedRun.model }}
526
- </p>
544
+ </SectionLabel>
527
545
  <p v-if="selectedRun.error" class="text-xs text-app-error-400">
528
546
  {{ selectedRun.error }}
529
547
  </p>
530
548
  <pre
531
549
  v-if="selectedRun.outputText"
532
- class="max-h-48 overflow-auto whitespace-pre-wrap rounded bg-app-950/60 p-2 text-[11px] text-toned"
550
+ class="max-h-48 overflow-auto whitespace-pre-wrap rounded-sm bg-app-950/60 p-2 text-2xs text-toned"
533
551
  >{{ selectedRun.outputText }}</pre>
534
552
  <div v-if="gradeByRun.get(selectedRun.id)" class="mt-2 space-y-0.5">
535
553
  <p
536
554
  v-for="d in gradeByRun.get(selectedRun.id)!.scores"
537
555
  :key="d.key"
538
- class="text-[11px] text-muted"
556
+ class="text-2xs text-muted"
539
557
  >
540
558
  <span :class="scoreColor(d.score)" class="font-semibold">{{ d.score }}</span>
541
559
  <span class="ms-1 text-toned">{{ d.key }}</span>
@@ -545,11 +563,13 @@ async function archive(prompt: SandboxPromptVersion) {
545
563
  </div>
546
564
  </div>
547
565
 
548
- <p class="text-[11px] uppercase tracking-wide text-dimmed">
566
+ <SectionLabel as="p">
549
567
  {{ t('sandbox.results.past') }}
550
- </p>
568
+ </SectionLabel>
551
569
  <div class="max-h-56 space-y-1 overflow-auto">
552
- <button
570
+ <UButton
571
+ color="neutral"
572
+ variant="ghost"
553
573
  v-for="x in store.experiments"
554
574
  :key="x.id"
555
575
  class="flex w-full items-center justify-between rounded-md border border-default bg-default/40 px-2 py-1.5 text-start text-sm hover:bg-elevated/50"
@@ -557,7 +577,7 @@ async function archive(prompt: SandboxPromptVersion) {
557
577
  >
558
578
  <span class="truncate text-toned">{{ x.name }}</span>
559
579
  <UBadge variant="soft" size="xs">{{ EXPERIMENT_STATUS_LABEL[x.status] }}</UBadge>
560
- </button>
580
+ </UButton>
561
581
  <p v-if="!store.experiments.length" class="text-xs text-dimmed">
562
582
  {{ t('sandbox.results.empty') }}
563
583
  </p>
@@ -591,15 +611,15 @@ async function archive(prompt: SandboxPromptVersion) {
591
611
  }}
592
612
  </UBadge>
593
613
  </div>
594
- <span class="text-[11px] text-dimmed">{{ p.agentKind }}</span>
614
+ <span class="text-2xs text-dimmed">{{ p.agentKind }}</span>
595
615
  </div>
596
616
  <div class="flex items-center gap-1">
597
- <UButton
617
+ <IconButton
598
618
  icon="i-lucide-pencil"
599
619
  color="neutral"
600
620
  variant="ghost"
601
621
  size="xs"
602
- :title="
622
+ :label="
603
623
  p.origin === 'baseline'
604
624
  ? t('sandbox.prompts.forkTitle')
605
625
  : t('sandbox.prompts.editTitle')
@@ -609,14 +629,14 @@ async function archive(prompt: SandboxPromptVersion) {
609
629
  <!-- Deploy: make this version the workspace's live prompt for its agent kind.
610
630
  Offered on a graded candidate and on an older workspace revision (rolling
611
631
  back), but not on the one already live, where it would be a no-op. -->
612
- <UButton
632
+ <IconButton
613
633
  v-if="canPromote(p)"
614
634
  icon="i-lucide-rocket"
615
635
  color="primary"
616
636
  variant="ghost"
617
637
  size="xs"
618
638
  :loading="promoting === p.id"
619
- :title="t('sandbox.prompts.promoteTitle')"
639
+ :label="t('sandbox.prompts.promoteTitle')"
620
640
  @click="promote(p)"
621
641
  />
622
642
  <UButton
@@ -632,13 +652,13 @@ async function archive(prompt: SandboxPromptVersion) {
632
652
  </div>
633
653
 
634
654
  <div v-if="editing" class="space-y-2 rounded-lg border border-muted bg-default/40 p-3">
635
- <p class="text-[11px] uppercase tracking-wide text-dimmed">
655
+ <SectionLabel as="p">
636
656
  {{
637
657
  editing.origin === 'baseline'
638
658
  ? t('sandbox.prompts.forkOf', { name: editing.name })
639
659
  : t('sandbox.prompts.newVersionOf', { name: editing.name })
640
660
  }}
641
- </p>
661
+ </SectionLabel>
642
662
  <UTextarea v-model="editText" :rows="16" class="w-full font-mono text-xs" autoresize />
643
663
  <div class="flex justify-end gap-2">
644
664
  <UButton
@@ -690,7 +710,7 @@ async function archive(prompt: SandboxPromptVersion) {
690
710
  </UBadge>
691
711
  </div>
692
712
  </div>
693
- <p v-if="f.objective?.kind === 'findings'" class="mt-0.5 text-[11px] text-dimmed">
713
+ <p v-if="f.objective?.kind === 'findings'" class="mt-0.5 text-2xs text-dimmed">
694
714
  {{
695
715
  t(
696
716
  'sandbox.fixtures.expectations',
@@ -259,7 +259,7 @@ async function revokeToken(key: PublicApiKey) {
259
259
  {{ boundLabel(key) }}
260
260
  </UBadge>
261
261
  </div>
262
- <div class="text-[11px] text-dimmed">
262
+ <div class="text-2xs text-dimmed">
263
263
  {{
264
264
  t('settings.apiTokens.list.created', {
265
265
  date: d(new Date(key.createdAt), 'short'),
@@ -5,6 +5,7 @@
5
5
  // operator sets a hard cap env var, the account/user input cannot exceed it and the cap is
6
6
  // shown here. See docs/initiatives/tiered-budgets.md.
7
7
  import { computed, reactive, ref, watch, type Ref } from 'vue'
8
+ import SectionLabel from '~/components/common/SectionLabel.vue'
8
9
 
9
10
  const { t, n } = useI18n()
10
11
  const toast = useToast()
@@ -122,7 +123,7 @@ function saveUser() {
122
123
 
123
124
  <template>
124
125
  <div class="space-y-8">
125
- <p class="text-[11px] text-muted">
126
+ <p class="text-2xs text-muted">
126
127
  {{ t('settings.workspaceSettings.budget.body') }}
127
128
  </p>
128
129
 
@@ -133,9 +134,9 @@ function saveUser() {
133
134
  </h3>
134
135
  <div class="grid grid-cols-2 gap-3">
135
136
  <label class="block">
136
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-dimmed">
137
+ <SectionLabel as="span" class="mb-1 block">
137
138
  {{ t('settings.workspaceSettings.budget.monthlyLimit') }}
138
- </span>
139
+ </SectionLabel>
139
140
  <UInput
140
141
  v-model="wsDraft.spendMonthlyLimit"
141
142
  type="number"
@@ -145,9 +146,9 @@ function saveUser() {
145
146
  />
146
147
  </label>
147
148
  <label class="block">
148
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-dimmed">
149
+ <SectionLabel as="span" class="mb-1 block">
149
150
  {{ t('settings.workspaceSettings.budget.currency') }}
150
- </span>
151
+ </SectionLabel>
151
152
  <UInput
152
153
  v-model="wsDraft.spendCurrency"
153
154
  placeholder="EUR"
@@ -157,7 +158,7 @@ function saveUser() {
157
158
  />
158
159
  </label>
159
160
  </div>
160
- <div v-if="workspace.spend" class="text-[11px] text-muted">
161
+ <div v-if="workspace.spend" class="text-2xs text-muted">
161
162
  {{
162
163
  t('settings.workspaceSettings.budget.spent', {
163
164
  spent: money(workspace.spend.costSpent, workspace.spend.currency),
@@ -183,13 +184,13 @@ function saveUser() {
183
184
  <h3 class="text-sm font-semibold text-default">
184
185
  {{ t('settings.workspaceSettings.budget.account') }}
185
186
  </h3>
186
- <p class="text-[11px] text-muted">
187
+ <p class="text-2xs text-muted">
187
188
  {{ t('settings.workspaceSettings.budget.accountBody') }}
188
189
  </p>
189
190
  <label class="block">
190
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-dimmed">
191
+ <SectionLabel as="span" class="mb-1 block">
191
192
  {{ t('settings.workspaceSettings.budget.monthlyLimit') }}
192
- </span>
193
+ </SectionLabel>
193
194
  <UInput
194
195
  v-model="accountDraft"
195
196
  type="number"
@@ -202,13 +203,13 @@ function saveUser() {
202
203
  </label>
203
204
  <p
204
205
  v-if="accountCap != null"
205
- class="text-[11px]"
206
+ class="text-2xs"
206
207
  :class="accountOverCap ? 'text-app-error-400' : 'text-app-warning-400'"
207
208
  >
208
209
  {{ t('settings.workspaceSettings.budget.hardCap', { amount: money(accountCap) }) }}
209
210
  <span class="text-dimmed">({{ t('settings.workspaceSettings.budget.hardCapHint') }})</span>
210
211
  </p>
211
- <div v-if="workspace.accountSpend" class="text-[11px] text-muted">
212
+ <div v-if="workspace.accountSpend" class="text-2xs text-muted">
212
213
  {{
213
214
  t('settings.workspaceSettings.budget.spent', {
214
215
  spent: money(workspace.accountSpend.costSpent),
@@ -216,7 +217,7 @@ function saveUser() {
216
217
  })
217
218
  }}
218
219
  </div>
219
- <p v-if="!canEditAccount" class="text-[11px] text-dimmed">
220
+ <p v-if="!canEditAccount" class="text-2xs text-dimmed">
220
221
  {{ t('settings.workspaceSettings.budget.adminOnly') }}
221
222
  </p>
222
223
  <div v-if="canEditAccount" class="flex justify-end">
@@ -238,13 +239,13 @@ function saveUser() {
238
239
  <h3 class="text-sm font-semibold text-default">
239
240
  {{ t('settings.workspaceSettings.budget.user') }}
240
241
  </h3>
241
- <p class="text-[11px] text-muted">
242
+ <p class="text-2xs text-muted">
242
243
  {{ t('settings.workspaceSettings.budget.userBody') }}
243
244
  </p>
244
245
  <label class="block">
245
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-dimmed">
246
+ <SectionLabel as="span" class="mb-1 block">
246
247
  {{ t('settings.workspaceSettings.budget.monthlyLimit') }}
247
- </span>
248
+ </SectionLabel>
248
249
  <UInput
249
250
  v-model="userDraft"
250
251
  type="number"
@@ -256,13 +257,13 @@ function saveUser() {
256
257
  </label>
257
258
  <p
258
259
  v-if="userCap != null"
259
- class="text-[11px]"
260
+ class="text-2xs"
260
261
  :class="userOverCap ? 'text-app-error-400' : 'text-app-warning-400'"
261
262
  >
262
263
  {{ t('settings.workspaceSettings.budget.hardCap', { amount: money(userCap) }) }}
263
264
  <span class="text-dimmed">({{ t('settings.workspaceSettings.budget.hardCapHint') }})</span>
264
265
  </p>
265
- <div v-if="workspace.userSpend" class="text-[11px] text-muted">
266
+ <div v-if="workspace.userSpend" class="text-2xs text-muted">
266
267
  {{
267
268
  t('settings.workspaceSettings.budget.spent', {
268
269
  spent: money(workspace.userSpend.costSpent),
@@ -173,7 +173,7 @@ async function removeKey(key: string) {
173
173
  </li>
174
174
  </ul>
175
175
 
176
- <p v-if="entry.stored && entry.updatedAt" class="text-[11px] text-dimmed">
176
+ <p v-if="entry.stored && entry.updatedAt" class="text-2xs text-dimmed">
177
177
  {{
178
178
  t('settings.capabilityCredentials.storedAt', {
179
179
  date: d(new Date(entry.updatedAt), 'short'),
@@ -191,13 +191,13 @@ async function removeKey(key: string) {
191
191
  usual one, but a facade that wired the store and dropped the flag lands here too, and
192
192
  blaming a custom resolver would make that wiring bug read as a deliberate configuration
193
193
  and send the operator to the one place that cannot explain it. -->
194
- <p v-else-if="view?.environmentFallback === true" class="text-[11px] text-dimmed">
194
+ <p v-else-if="view?.environmentFallback === true" class="text-2xs text-dimmed">
195
195
  {{ t('settings.capabilityCredentials.notStoredWithFallback') }}
196
196
  </p>
197
- <p v-else-if="view?.environmentFallback === false" class="text-[11px] text-app-warning-400">
197
+ <p v-else-if="view?.environmentFallback === false" class="text-2xs text-app-warning-400">
198
198
  {{ t('settings.capabilityCredentials.notStored') }}
199
199
  </p>
200
- <p v-else class="text-[11px] text-dimmed">
200
+ <p v-else class="text-2xs text-dimmed">
201
201
  {{ t('settings.capabilityCredentials.notStoredUnknownFallback') }}
202
202
  </p>
203
203
 
@@ -268,7 +268,7 @@ async function removeKey(key: string) {
268
268
  >
269
269
  <div class="min-w-0">
270
270
  <code class="font-mono text-sm">{{ orphan.key }}</code>
271
- <span class="block text-[11px] text-dimmed">
271
+ <span class="block text-2xs text-dimmed">
272
272
  {{
273
273
  t('settings.capabilityCredentials.storedAt', {
274
274
  date: d(new Date(orphan.updatedAt), 'short'),
@@ -147,7 +147,7 @@ async function remove() {
147
147
  <h3 class="text-sm font-semibold text-default">
148
148
  {{ t('inspector.testConfig.provisionTypes.cloudflare') }}
149
149
  </h3>
150
- <p class="text-[11px] text-muted">
150
+ <p class="text-2xs text-muted">
151
151
  {{ t('settings.infrastructure.cloudflare.intro') }}
152
152
  </p>
153
153
 
@@ -162,7 +162,7 @@ async function remove() {
162
162
  disabled
163
163
  size="lg"
164
164
  :label="t('settings.infrastructure.handler.connectionEstablished')"
165
- :ui="{ label: 'text-[13px] font-semibold text-app-success-300' }"
165
+ :ui="{ label: 'text-sm font-semibold text-app-success-300' }"
166
166
  />
167
167
  <div class="flex items-center gap-1">
168
168
  <UButton
@@ -185,31 +185,31 @@ async function remove() {
185
185
  />
186
186
  </div>
187
187
  </div>
188
- <p class="pl-7 text-[11px] text-toned">
188
+ <p class="pl-7 text-2xs text-toned">
189
189
  {{ t('settings.infrastructure.cloudflare.connectedAs', { subdomain: workersSubdomain }) }}
190
190
  </p>
191
191
  </div>
192
192
 
193
193
  <div v-else class="space-y-2" data-testid="cloudflare-handler-form">
194
194
  <div class="space-y-1">
195
- <label class="text-[11px] text-muted">{{
195
+ <label class="text-2xs text-muted">{{
196
196
  t('settings.infrastructure.cloudflare.label')
197
197
  }}</label>
198
198
  <UInput v-model="label" size="xs" />
199
199
  </div>
200
200
 
201
201
  <div class="space-y-1">
202
- <label class="text-[11px] text-muted">{{
202
+ <label class="text-2xs text-muted">{{
203
203
  t('settings.infrastructure.cloudflare.subdomain')
204
204
  }}</label>
205
205
  <UInput v-model="workersSubdomain" size="xs" placeholder="my-account" />
206
- <p class="text-[11px] text-dimmed">
206
+ <p class="text-2xs text-dimmed">
207
207
  {{ t('settings.infrastructure.cloudflare.subdomainHint') }}
208
208
  </p>
209
209
  </div>
210
210
 
211
211
  <div class="space-y-1">
212
- <label class="text-[11px] text-muted">{{
212
+ <label class="text-2xs text-muted">{{
213
213
  t('settings.infrastructure.cloudflare.token')
214
214
  }}</label>
215
215
  <UInput
@@ -222,55 +222,58 @@ async function remove() {
222
222
  : t('settings.infrastructure.cloudflare.tokenPlaceholder')
223
223
  "
224
224
  />
225
- <p class="text-[11px] text-dimmed">
225
+ <p class="text-2xs text-dimmed">
226
226
  {{ t('settings.infrastructure.cloudflare.tokenHint') }}
227
227
  </p>
228
228
  </div>
229
229
 
230
230
  <div class="space-y-1">
231
- <label class="text-[11px] text-muted">{{
231
+ <label class="text-2xs text-muted">{{
232
232
  t('settings.infrastructure.cloudflare.repo')
233
233
  }}</label>
234
234
  <UInput v-model="repo" size="xs" placeholder="owner/repo" />
235
- <p class="text-[11px] text-dimmed">
235
+ <p class="text-2xs text-dimmed">
236
236
  {{ t('settings.infrastructure.cloudflare.repoHint') }}
237
237
  </p>
238
238
  </div>
239
239
 
240
- <details class="rounded-md border border-muted/70 p-2">
241
- <summary class="cursor-pointer text-[11px] text-muted">
242
- {{ t('settings.infrastructure.cloudflare.advanced') }}
243
- </summary>
244
- <div class="mt-2 space-y-2">
245
- <div class="space-y-1">
246
- <label class="text-[11px] text-muted">{{
247
- t('settings.infrastructure.cloudflare.workerTemplate')
248
- }}</label>
240
+ <UCollapsible class="rounded-md border border-muted/70 p-2">
241
+ <template #default="{ open }">
242
+ <UButton
243
+ variant="link"
244
+ color="neutral"
245
+ size="xs"
246
+ :icon="open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
247
+ :label="t('settings.infrastructure.cloudflare.advanced')"
248
+ :ui="{ base: 'w-full justify-start gap-1 p-0 text-2xs text-muted' }"
249
+ />
250
+ </template>
251
+ <template #content>
252
+ <div class="mt-2 space-y-2">
249
253
  <!-- The placeholder is a FORMAT EXAMPLE containing vue-i18n metacharacters, so it
250
254
  stays inline rather than becoming a catalog key. -->
251
- <UInput
252
- v-model="workerNameTemplate"
255
+ <UFormField size="xs" :label="t('settings.infrastructure.cloudflare.workerTemplate')">
256
+ <UInput
257
+ v-model="workerNameTemplate"
258
+ size="xs"
259
+ placeholder="cat-factory-pr-{{pullNumber}}"
260
+ />
261
+ </UFormField>
262
+ <UFormField
253
263
  size="xs"
254
- placeholder="cat-factory-pr-{{pullNumber}}"
255
- />
264
+ :label="t('settings.infrastructure.cloudflare.environmentTemplate')"
265
+ >
266
+ <UInput v-model="environmentNameTemplate" size="xs" placeholder="pr-{{pullNumber}}" />
267
+ </UFormField>
268
+ <p class="text-2xs text-dimmed">
269
+ {{ t('settings.infrastructure.cloudflare.templateHint') }}
270
+ </p>
271
+ <UFormField size="xs" :label="t('settings.infrastructure.cloudflare.apiBaseUrl')">
272
+ <UInput v-model="apiBaseUrl" size="xs" placeholder="https://api.github.com" />
273
+ </UFormField>
256
274
  </div>
257
- <div class="space-y-1">
258
- <label class="text-[11px] text-muted">{{
259
- t('settings.infrastructure.cloudflare.environmentTemplate')
260
- }}</label>
261
- <UInput v-model="environmentNameTemplate" size="xs" placeholder="pr-{{pullNumber}}" />
262
- </div>
263
- <p class="text-[11px] text-dimmed">
264
- {{ t('settings.infrastructure.cloudflare.templateHint') }}
265
- </p>
266
- <div class="space-y-1">
267
- <label class="text-[11px] text-muted">{{
268
- t('settings.infrastructure.cloudflare.apiBaseUrl')
269
- }}</label>
270
- <UInput v-model="apiBaseUrl" size="xs" placeholder="https://api.github.com" />
271
- </div>
272
- </div>
273
- </details>
275
+ </template>
276
+ </UCollapsible>
274
277
 
275
278
  <div class="flex items-center gap-2">
276
279
  <UButton size="xs" :disabled="!canSave || busy" @click="save">
@@ -300,7 +303,7 @@ async function remove() {
300
303
 
301
304
  <p
302
305
  v-if="testResult"
303
- class="text-[11px]"
306
+ class="text-2xs"
304
307
  :class="testResult.ok ? 'text-app-success-300' : 'text-app-error-300'"
305
308
  >
306
309
  {{ testResult.message }}