@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
@@ -28,6 +28,7 @@ import type { BugFishingFinding, BugFishingSeverity, BugFishingStepState } from
28
28
  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
+ import SectionLabel from '~/components/common/SectionLabel.vue'
31
32
 
32
33
  const execution = useExecutionStore()
33
34
  const board = useBoardStore()
@@ -263,12 +264,13 @@ const PHASE_ICON: Record<string, string> = {
263
264
  data-testid="bug-fishing-phases"
264
265
  class="w-60 shrink-0 overflow-y-auto border-r border-default px-3 py-4"
265
266
  >
266
- <p class="mb-2 px-1 text-[10px] font-semibold uppercase tracking-wide text-dimmed">
267
+ <SectionLabel as="p" class="mb-2 px-1">
267
268
  {{ t('bugFishing.phases.heading') }}
268
- </p>
269
- <button
270
- type="button"
271
- class="mb-1 w-full rounded-md px-2 py-1.5 text-left text-[12px]"
269
+ </SectionLabel>
270
+ <UButton
271
+ color="neutral"
272
+ variant="ghost"
273
+ class="mb-1 w-full rounded-md px-2 py-1.5 text-left text-xs"
272
274
  :class="
273
275
  selectedPassKey === null
274
276
  ? 'bg-elevated text-app-100'
@@ -277,21 +279,22 @@ const PHASE_ICON: Record<string, string> = {
277
279
  @click="selectedPassKey = null"
278
280
  >
279
281
  {{ t('bugFishing.phases.all', { count: findings.length }) }}
280
- </button>
282
+ </UButton>
281
283
  <!-- Grouped by TERRITORY on a partitioned codebase. A codebase small enough to fish
282
284
  whole has one group with no label, which renders as the flat angle rail. -->
283
285
  <div v-for="group in territoryGroups" :key="group.id ?? 'whole'" class="mb-2">
284
286
  <p
285
287
  v-if="group.label"
286
- class="mb-1 mt-2 truncate px-1 text-[10px] font-semibold uppercase tracking-wide text-app-info-400/80"
288
+ class="mb-1 mt-2 truncate px-1 text-3xs font-semibold uppercase tracking-wide text-app-info-400/80"
287
289
  :title="group.label"
288
290
  >
289
291
  {{ group.label }}
290
292
  </p>
291
293
  <ul class="space-y-0.5">
292
294
  <li v-for="phase in group.passes" :key="passKey(phase)">
293
- <button
294
- type="button"
295
+ <UButton
296
+ color="neutral"
297
+ variant="ghost"
295
298
  class="flex w-full items-start gap-2 rounded-md px-2 py-1.5 text-left"
296
299
  :class="
297
300
  selectedPassKey === passKey(phase)
@@ -312,8 +315,8 @@ const PHASE_ICON: Record<string, string> = {
312
315
  }"
313
316
  />
314
317
  <span class="min-w-0 flex-1">
315
- <span class="block truncate text-[12px]">{{ phase.title }}</span>
316
- <span class="block text-[10px] text-dimmed">
318
+ <span class="block truncate text-xs">{{ phase.title }}</span>
319
+ <span class="block text-3xs text-dimmed">
317
320
  {{
318
321
  phase.status === 'completed' || phase.status === 'failed'
319
322
  ? t('bugFishing.phases.found', { count: phaseFindingCount(phase) })
@@ -335,12 +338,12 @@ const PHASE_ICON: Record<string, string> = {
335
338
  :style="{ width: `${coverageShare(phase)}%` }"
336
339
  />
337
340
  </span>
338
- <span class="mt-0.5 block text-[10px] text-app-600">
341
+ <span class="mt-0.5 block text-3xs text-app-600">
339
342
  {{ t('bugFishing.coverage.share', { percent: coverageShare(phase) }) }}
340
343
  </span>
341
344
  </span>
342
345
  </span>
343
- </button>
346
+ </UButton>
344
347
  </li>
345
348
  </ul>
346
349
  </div>
@@ -354,18 +357,18 @@ const PHASE_ICON: Record<string, string> = {
354
357
  v-if="selectedPhase"
355
358
  class="mb-4 rounded-lg border border-default bg-default/40 px-3 py-2.5"
356
359
  >
357
- <p class="text-[11px] text-muted">{{ selectedPhase.goal }}</p>
360
+ <p class="text-2xs text-muted">{{ selectedPhase.goal }}</p>
358
361
  <p
359
362
  v-if="selectedPhase.status === 'failed'"
360
363
  data-testid="bug-fishing-phase-failed"
361
- class="mt-2 text-[12px] text-app-warning-300"
364
+ class="mt-2 text-xs text-app-warning-300"
362
365
  >
363
366
  {{ t('bugFishing.phases.failedNote', { reason: selectedPhase.failureReason ?? '' }) }}
364
367
  </p>
365
368
  <MarkdownProse
366
369
  v-else-if="selectedPhase.summary"
367
370
  :text="selectedPhase.summary"
368
- class="mt-2 max-w-3xl text-[12px]"
371
+ class="mt-2 max-w-3xl text-xs"
369
372
  />
370
373
  </div>
371
374
 
@@ -374,7 +377,7 @@ const PHASE_ICON: Record<string, string> = {
374
377
  <div
375
378
  v-if="fishing"
376
379
  data-testid="bug-fishing-in-progress"
377
- 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-[12px] text-app-info-200"
380
+ 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"
378
381
  >
379
382
  <UIcon name="i-lucide-loader-circle" class="h-4 w-4 shrink-0 animate-spin" />
380
383
  <span>
@@ -395,7 +398,7 @@ const PHASE_ICON: Record<string, string> = {
395
398
  <div
396
399
  v-if="plan && (unfished.length > 0 || plan.treeTruncated || plan.surveyUnavailableReason)"
397
400
  data-testid="bug-fishing-plan"
398
- class="mb-4 rounded-lg border border-app-warning-500/25 bg-app-warning-500/5 px-3 py-2 text-[12px] text-app-warning-200"
401
+ class="mb-4 rounded-lg border border-app-warning-500/25 bg-app-warning-500/5 px-3 py-2 text-xs text-app-warning-200"
399
402
  >
400
403
  <p v-if="plan.surveyUnavailableReason" data-testid="bug-fishing-survey-unavailable">
401
404
  {{ t('bugFishing.plan.surveyUnavailable', { reason: plan.surveyUnavailableReason }) }}
@@ -412,7 +415,7 @@ const PHASE_ICON: Record<string, string> = {
412
415
  })
413
416
  }}
414
417
  </p>
415
- <ul class="mt-1 space-y-0.5 text-[11px] text-app-warning-200/80">
418
+ <ul class="mt-1 space-y-0.5 text-2xs text-app-warning-200/80">
416
419
  <li v-for="cell in unfished" :key="`${cell.territoryId}::${cell.phaseId}`">
417
420
  {{
418
421
  t('bugFishing.plan.unfishedCell', {
@@ -432,7 +435,7 @@ const PHASE_ICON: Record<string, string> = {
432
435
  data-testid="bug-fishing-pipeline"
433
436
  class="mb-4 flex flex-wrap items-center gap-2 rounded-lg border border-default bg-default/40 px-3 py-2"
434
437
  >
435
- <span class="text-[11px] text-muted">{{ t('bugFishing.fixPipeline.label') }}</span>
438
+ <span class="text-2xs text-muted">{{ t('bugFishing.fixPipeline.label') }}</span>
436
439
  <USelectMenu
437
440
  v-model="pipelineOverride"
438
441
  :items="pipelineOptions"
@@ -440,22 +443,19 @@ const PHASE_ICON: Record<string, string> = {
440
443
  size="xs"
441
444
  class="min-w-52"
442
445
  />
443
- <span class="text-[11px] text-dimmed">
446
+ <span class="text-2xs text-dimmed">
444
447
  {{ t('bugFishing.fixPipeline.hint', { pipeline: effectivePipelineName ?? '—' }) }}
445
448
  </span>
446
449
  </div>
447
450
 
448
451
  <div class="mb-2 flex items-center justify-between">
449
- <p class="text-[11px] text-muted">
452
+ <p class="text-2xs text-muted">
450
453
  {{ t('bugFishing.counts', { untriaged: untriagedCount, spawned: spawnedCount }) }}
451
454
  </p>
452
- <label class="flex items-center gap-1.5 text-[11px] text-muted">
453
- <input v-model="showTriaged" type="checkbox" class="accent-app-info-500" />
454
- {{ t('bugFishing.showTriaged') }}
455
- </label>
455
+ <UCheckbox v-model="showTriaged" size="xs" :label="t('bugFishing.showTriaged')" />
456
456
  </div>
457
457
 
458
- <p v-if="bugFishing.error" class="mb-3 text-[12px] text-app-error-300">
458
+ <p v-if="bugFishing.error" class="mb-3 text-xs text-app-error-300">
459
459
  {{ bugFishing.error }}
460
460
  </p>
461
461
 
@@ -465,7 +465,7 @@ const PHASE_ICON: Record<string, string> = {
465
465
  <div
466
466
  v-if="visibleFindings.length === 0"
467
467
  data-testid="bug-fishing-empty"
468
- class="rounded-lg border border-default bg-default/40 px-4 py-8 text-center text-[12px] text-muted"
468
+ class="rounded-lg border border-default bg-default/40 px-4 py-8 text-center text-xs text-muted"
469
469
  >
470
470
  {{
471
471
  findings.length === 0
@@ -484,54 +484,76 @@ const PHASE_ICON: Record<string, string> = {
484
484
  >
485
485
  <div class="flex flex-wrap items-center gap-2">
486
486
  <span
487
- class="rounded border px-1.5 py-0.5 text-[10px] font-semibold uppercase"
487
+ class="rounded-sm border px-1.5 py-0.5 text-3xs font-semibold uppercase"
488
488
  :class="SEVERITY_CLASS[finding.severity]"
489
489
  >
490
490
  {{ t(`bugFishing.severity.${finding.severity}`) }}
491
491
  </span>
492
- <span class="rounded bg-elevated px-1.5 py-0.5 text-[10px] text-toned">
492
+ <span class="rounded-sm bg-elevated px-1.5 py-0.5 text-3xs text-toned">
493
493
  {{ t(`bugFishing.kind.${finding.kind}`) }}
494
494
  </span>
495
- <span class="text-[10px] text-dimmed">
495
+ <span class="text-3xs text-dimmed">
496
496
  {{ t(`bugFishing.confidence.${finding.confidence}`) }}
497
497
  </span>
498
498
  <span
499
- class="min-w-0 flex-1 text-[13px] text-app-100"
499
+ class="min-w-0 flex-1 text-sm text-app-100"
500
500
  :class="{ 'line-through': finding.dismissed }"
501
501
  >
502
502
  {{ finding.title }}
503
503
  </span>
504
504
  </div>
505
505
 
506
- <p v-if="finding.path" class="mt-1 font-mono text-[11px] text-dimmed">
506
+ <p v-if="finding.path" class="mt-1 font-mono text-2xs text-dimmed">
507
507
  {{ finding.path }}<span v-if="finding.line">:{{ finding.line }}</span>
508
508
  </p>
509
509
 
510
- <MarkdownProse :text="finding.detail" class="mt-2 max-w-3xl text-[12px]" />
510
+ <MarkdownProse :text="finding.detail" class="mt-2 max-w-3xl text-xs" />
511
511
 
512
- <div v-if="finding.failureScenario" class="mt-2 text-[12px] text-toned">
513
- <span class="text-[10px] font-semibold uppercase tracking-wide text-dimmed">
512
+ <div v-if="finding.failureScenario" class="mt-2 text-xs text-toned">
513
+ <SectionLabel as="span">
514
514
  {{ t('bugFishing.finding.failureScenario') }}
515
- </span>
515
+ </SectionLabel>
516
516
  <MarkdownProse :text="finding.failureScenario" class="mt-0.5 max-w-3xl" />
517
517
  </div>
518
518
 
519
519
  <!-- Evidence is rendered apart from the detail for the reason the contract keeps them
520
520
  apart: a finding that cannot point at the code it describes is speculating, and
521
521
  that should be visible without reading the prose for it. -->
522
- <details v-if="finding.evidence" class="mt-2">
523
- <summary class="cursor-pointer text-[11px] text-muted hover:text-default">
524
- {{ t('bugFishing.finding.evidence') }}
525
- </summary>
526
- <MarkdownProse :text="finding.evidence" class="mt-1 max-w-3xl text-[12px]" />
527
- </details>
528
-
529
- <details v-if="finding.suggestedFix" class="mt-1">
530
- <summary class="cursor-pointer text-[11px] text-muted hover:text-default">
531
- {{ t('bugFishing.finding.suggestedFix') }}
532
- </summary>
533
- <MarkdownProse :text="finding.suggestedFix" class="mt-1 max-w-3xl text-[12px]" />
534
- </details>
522
+ <UCollapsible v-if="finding.evidence" class="mt-2">
523
+ <template #default="{ open }">
524
+ <UButton
525
+ variant="link"
526
+ color="neutral"
527
+ size="xs"
528
+ :icon="open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
529
+ :label="t('bugFishing.finding.evidence')"
530
+ :ui="{
531
+ base: 'w-full justify-start gap-1 p-0 text-2xs text-muted hover:text-default',
532
+ }"
533
+ />
534
+ </template>
535
+ <template #content>
536
+ <MarkdownProse :text="finding.evidence" class="mt-1 max-w-3xl text-xs" />
537
+ </template>
538
+ </UCollapsible>
539
+
540
+ <UCollapsible v-if="finding.suggestedFix" class="mt-1">
541
+ <template #default="{ open }">
542
+ <UButton
543
+ variant="link"
544
+ color="neutral"
545
+ size="xs"
546
+ :icon="open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
547
+ :label="t('bugFishing.finding.suggestedFix')"
548
+ :ui="{
549
+ base: 'w-full justify-start gap-1 p-0 text-2xs text-muted hover:text-default',
550
+ }"
551
+ />
552
+ </template>
553
+ <template #content>
554
+ <MarkdownProse :text="finding.suggestedFix" class="mt-1 max-w-3xl text-xs" />
555
+ </template>
556
+ </UCollapsible>
535
557
 
536
558
  <!-- Already marked: say what was created and let the reader follow it. The three
537
559
  spawn states are rendered apart because they are three different facts — a task
@@ -540,7 +562,7 @@ const PHASE_ICON: Record<string, string> = {
540
562
  <div
541
563
  v-if="finding.spawn?.status === 'spawned'"
542
564
  data-testid="bug-fishing-finding-spawned"
543
- class="mt-2 flex flex-wrap items-center gap-2 text-[11px] text-app-success-300"
565
+ class="mt-2 flex flex-wrap items-center gap-2 text-2xs text-app-success-300"
544
566
  >
545
567
  <UIcon name="i-lucide-check-circle-2" class="h-3.5 w-3.5" />
546
568
  <span>
@@ -552,13 +574,14 @@ const PHASE_ICON: Record<string, string> = {
552
574
  })
553
575
  }}
554
576
  </span>
555
- <button
556
- type="button"
557
- class="underline hover:text-app-success-200"
577
+ <UButton
578
+ color="neutral"
579
+ variant="link"
580
+ class="p-0 text-2xs underline hover:text-app-success-200"
558
581
  @click="openSpawnedTask(finding.spawn.taskId)"
559
582
  >
560
583
  {{ t('bugFishing.finding.openTask') }}
561
- </button>
584
+ </UButton>
562
585
  </div>
563
586
 
564
587
  <!-- A claim held by a marking still in flight. No task to link yet, and no Fix
@@ -566,7 +589,7 @@ const PHASE_ICON: Record<string, string> = {
566
589
  <div
567
590
  v-else-if="finding.spawn?.status === 'pending'"
568
591
  data-testid="bug-fishing-finding-spawning"
569
- class="mt-2 flex flex-wrap items-center gap-2 text-[11px] text-muted"
592
+ class="mt-2 flex flex-wrap items-center gap-2 text-2xs text-muted"
570
593
  >
571
594
  <UIcon name="i-lucide-loader-circle" class="h-3.5 w-3.5 animate-spin" />
572
595
  <span>{{ t('bugFishing.finding.spawning') }}</span>
@@ -577,7 +600,7 @@ const PHASE_ICON: Record<string, string> = {
577
600
  <p
578
601
  v-else-if="finding.spawn?.status === 'failed'"
579
602
  data-testid="bug-fishing-finding-spawn-failed"
580
- class="mt-2 max-w-3xl text-[11px] text-app-warning-300"
603
+ class="mt-2 max-w-3xl text-2xs text-app-warning-300"
581
604
  >
582
605
  {{
583
606
  t('bugFishing.finding.spawnFailed', {
@@ -623,7 +646,7 @@ const PHASE_ICON: Record<string, string> = {
623
646
  v-if="awaiting"
624
647
  class="flex items-center justify-between gap-3 border-t border-default px-5 py-3"
625
648
  >
626
- <p class="text-[11px] text-dimmed">
649
+ <p class="text-2xs text-dimmed">
627
650
  {{
628
651
  untriagedCount > 0
629
652
  ? t('bugFishing.footer.parkedWithUntriaged', { count: untriagedCount })
@@ -19,6 +19,7 @@ import type {
19
19
  ReviewItemSeverity,
20
20
  ReviewItemStatus,
21
21
  } from '~/types/clarity'
22
+ import SectionLabel from '~/components/common/SectionLabel.vue'
22
23
 
23
24
  const board = useBoardStore()
24
25
  const clarity = useClarityStore()
@@ -424,9 +425,9 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
424
425
  v-if="item.reply && item.status !== 'open' && item.status !== 'answered'"
425
426
  class="mt-2 max-w-3xl rounded-md border-s-2 border-muted bg-app-950/40 px-3 py-1.5 text-sm text-toned"
426
427
  >
427
- <span class="text-[10px] uppercase tracking-wide text-dimmed">
428
+ <SectionLabel as="span">
428
429
  {{ t('clarity.answerLabel') }}
429
- </span>
430
+ </SectionLabel>
430
431
  <p class="whitespace-pre-line">{{ item.reply }}</p>
431
432
  </div>
432
433
 
@@ -480,7 +481,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
480
481
 
481
482
  <!-- clarified document: the standard-format bug report -->
482
483
  <section v-if="outline" class="mt-6 border-t border-default pt-5">
483
- <div class="mb-3 flex items-center gap-1.5 text-[11px] text-app-success-400">
484
+ <div class="mb-3 flex items-center gap-1.5 text-2xs text-app-success-400">
484
485
  <UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
485
486
  <span class="font-semibold uppercase tracking-wide">
486
487
  {{ incorporated ? t('clarity.docHeading') : t('clarity.docHeadingDraft') }}
@@ -490,9 +491,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
490
491
  `width` prop): the window is `full`-width now, and this is continuous prose that
491
492
  would otherwise run to 200-character lines. -->
492
493
  <div v-for="s in outline.sections" :key="s.id" class="mb-2 max-w-3xl">
493
- <button
494
+ <UButton
495
+ color="neutral"
496
+ variant="ghost"
494
497
  v-if="s.title"
495
- class="group flex w-full items-center gap-2 text-start"
498
+ class="group flex w-full items-center gap-2 p-0 text-start hover:bg-transparent"
496
499
  @click="toggle(s.id)"
497
500
  >
498
501
  <UIcon
@@ -505,10 +508,10 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
505
508
  :class="s.depth <= 1 ? 'text-base' : s.depth === 2 ? 'text-sm' : 'text-xs'"
506
509
  v-html="s.titleHtml"
507
510
  />
508
- </button>
511
+ </UButton>
509
512
  <div
510
513
  v-show="!s.title || !collapsed[s.id]"
511
- class="reader-prose mt-1 ps-5.5 text-[13px] leading-relaxed text-toned"
514
+ class="reader-prose mt-1 ps-5.5 text-sm leading-relaxed text-toned"
512
515
  v-html="s.bodyHtml"
513
516
  />
514
517
  </div>
@@ -574,7 +577,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
574
577
  >
575
578
  {{ t('clarity.incorporateAnswers') }}
576
579
  </UButton>
577
- <p class="text-[11px] leading-relaxed text-dimmed">
580
+ <p class="text-2xs leading-relaxed text-dimmed">
578
581
  <template v-if="canProceed">
579
582
  {{ t('clarity.hint.proceed') }}
580
583
  </template>
@@ -636,14 +639,14 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
636
639
  {{ t('clarity.redoWithDirection') }}
637
640
  </UButton>
638
641
  </div>
639
- <p class="text-[11px] leading-relaxed text-dimmed">
642
+ <p class="text-2xs leading-relaxed text-dimmed">
640
643
  {{ t('clarity.redoHint') }}
641
644
  </p>
642
645
  </div>
643
646
 
644
647
  <div
645
648
  v-if="review && incorporated"
646
- class="border-t border-default pt-4 text-[11px] leading-relaxed text-dimmed"
649
+ class="border-t border-default pt-4 text-2xs leading-relaxed text-dimmed"
647
650
  >
648
651
  {{ t('clarity.incorporatedFooter') }}
649
652
  </div>
@@ -19,7 +19,7 @@ function reload() {
19
19
  <UIcon name="i-lucide-unplug" class="h-4 w-4 shrink-0 text-app-warning-400" />
20
20
  {{ t('errors.asyncView.title') }}
21
21
  </div>
22
- <p class="mt-2 text-[12px] leading-relaxed text-muted">
22
+ <p class="mt-2 text-xs leading-relaxed text-muted">
23
23
  {{ t('errors.asyncView.body') }}
24
24
  </p>
25
25
  <UButton class="mt-4" color="primary" variant="soft" size="xs" @click="reload">
@@ -51,16 +51,16 @@ const draft = computed({
51
51
  <template>
52
52
  <div class="rounded-lg border border-default bg-app-950/40 p-3" data-testid="clarification-item">
53
53
  <div class="flex items-start justify-between gap-2">
54
- <p class="text-[13px] font-medium text-default">{{ prompt }}</p>
54
+ <p class="text-sm font-medium text-default">{{ prompt }}</p>
55
55
  <slot name="badges" />
56
56
  </div>
57
- <p v-if="detail" class="mt-1 whitespace-pre-wrap text-[12px] leading-relaxed text-muted">
57
+ <p v-if="detail" class="mt-1 whitespace-pre-wrap text-xs leading-relaxed text-muted">
58
58
  {{ detail }}
59
59
  </p>
60
60
 
61
61
  <!-- dismissed: a "not relevant" chip + reopen -->
62
62
  <div v-if="dismissed" class="mt-2 flex items-center justify-between gap-2">
63
- <span class="inline-flex items-center gap-1 text-[11px] text-dimmed">
63
+ <span class="inline-flex items-center gap-1 text-2xs text-dimmed">
64
64
  <UIcon name="i-lucide-x" class="h-3.5 w-3.5" />{{ t('clarification.dismissed') }}
65
65
  </span>
66
66
  <UButton
@@ -79,7 +79,7 @@ const draft = computed({
79
79
  <!-- a recommendation is being requested/generated: a working chip, no answer box -->
80
80
  <div
81
81
  v-else-if="requested"
82
- class="mt-2 inline-flex items-center gap-1 text-[11px] text-primary"
82
+ class="mt-2 inline-flex items-center gap-1 text-2xs text-primary"
83
83
  data-testid="clarification-requested"
84
84
  >
85
85
  <UIcon name="i-lucide-loader-circle" class="h-3.5 w-3.5 animate-spin" />
@@ -131,10 +131,10 @@ const draft = computed({
131
131
  class="mt-2 rounded-md border border-primary/50 bg-primary/10 p-2"
132
132
  data-testid="clarification-recommendation"
133
133
  >
134
- <div class="mb-1 flex items-center gap-1 text-[10px] uppercase tracking-wide text-primary">
134
+ <div class="mb-1 flex items-center gap-1 text-3xs uppercase tracking-wide text-primary">
135
135
  <UIcon name="i-lucide-wand-2" class="h-3 w-3" />{{ t('clarification.suggestion') }}
136
136
  </div>
137
- <p class="whitespace-pre-wrap text-[12px] text-default">{{ recommendation }}</p>
137
+ <p class="whitespace-pre-wrap text-xs text-default">{{ recommendation }}</p>
138
138
  <UButton
139
139
  class="mt-1.5"
140
140
  size="xs"
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  // Standard icon-only "copy to clipboard" button with confirmation feedback. Routes through
3
3
  // `useCopyToClipboard` so success/failure is always toasted (UX-38), and carries both a
4
- // `title` and an `aria-label` so it's named for pointer tooltips and screen readers alike.
5
- // Used to make error/detail surfaces copyable (UX-39) — the first thing a user does with a
4
+ // tooltip and an `aria-label` so it's named for pointer users and screen readers alike.
5
+ // Used to make error/detail surfaces copyable (UX-39): the first thing a user does with a
6
6
  // stack trace or failure summary is copy it.
7
7
  const props = defineProps<{
8
8
  /** The text to place on the clipboard. */
@@ -18,13 +18,14 @@ const label = computed(() => props.label ?? t('common.copy'))
18
18
  </script>
19
19
 
20
20
  <template>
21
- <UButton
22
- icon="i-lucide-copy"
23
- color="neutral"
24
- variant="ghost"
25
- :size="size ?? 'xs'"
26
- :title="label"
27
- :aria-label="label"
28
- @click.stop="copy(text)"
29
- />
21
+ <UTooltip :text="label">
22
+ <UButton
23
+ icon="i-lucide-copy"
24
+ color="neutral"
25
+ variant="ghost"
26
+ :size="size ?? 'xs'"
27
+ :aria-label="label"
28
+ @click.stop="copy(text)"
29
+ />
30
+ </UTooltip>
30
31
  </template>
@@ -112,7 +112,7 @@ function selectItems(field: DescriptorField) {
112
112
  testid that would have to be slugified to be selectable. -->
113
113
  <p
114
114
  v-if="caption"
115
- class="-mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted"
115
+ class="-mb-1 text-2xs font-semibold uppercase tracking-wide text-muted"
116
116
  :class="{ 'pt-2': startsGroup }"
117
117
  :data-testid="`${testidPrefix}-section`"
118
118
  >
@@ -25,7 +25,7 @@ withDefaults(
25
25
  <p :class="compact ? 'text-xs' : 'text-sm'" class="font-medium text-muted">
26
26
  {{ title }}
27
27
  </p>
28
- <p v-if="description" :class="compact ? 'text-[11px]' : 'text-xs'" class="max-w-xs">
28
+ <p v-if="description" :class="compact ? 'text-2xs' : 'text-xs'" class="max-w-xs">
29
29
  {{ description }}
30
30
  </p>
31
31
  <div v-if="$slots.default" :class="compact ? 'mt-1' : 'mt-2'">
@@ -1,19 +1,44 @@
1
1
  <script setup lang="ts">
2
2
  // Shared icon-only button primitive that ENFORCES the accessible-name convention (UX-62/63):
3
- // every icon button must carry a `label`, applied as BOTH `title` (pointer tooltip) and
4
- // `aria-label` (screen readers) — `title` alone never fires on touch and is invisible to
5
- // assistive tech, so an unlabeled icon button is a bug this component makes unrepresentable.
6
- // All other UButton props/listeners (icon, color, variant, size, @click, :loading, :disabled)
7
- // pass straight through via `$attrs`; `label` is declared as a prop so it strips off before
8
- // reaching UButton (whose own `label` prop would otherwise render visible text). Mirrors the
9
- // shape of `common/CopyButton.vue`.
3
+ // every icon button must carry a `label`, applied as BOTH the tooltip and `aria-label` (screen
4
+ // readers): an unlabeled icon button is a bug this component makes unrepresentable.
5
+ //
6
+ // The hint is a UTooltip, not a `title` attribute: `title` never fires on touch, is not styled,
7
+ // takes about a second to appear, and is not what the rest of the app's tooltips look like.
8
+ // `aria-label` stays on the button itself, because the tooltip is a hover/focus affordance and
9
+ // the accessible NAME has to be there whether or not one ever opens.
10
+ //
11
+ // All other UButton props/listeners (icon, color, variant, size, `ui`, @click, :loading,
12
+ // :disabled) pass straight through via `$attrs`; `label` is declared as a prop so it strips off
13
+ // before reaching UButton (whose own `label` prop would otherwise render visible text).
14
+ //
15
+ // `class` and `style` land on the WRAPPER, not the button: the wrapper is the box the parent lays
16
+ // out, so `ms-auto`, `shrink-0` or Vue Flow's `nodrag` only work there. The button's own look is
17
+ // its props, and `:ui="{ base: '…' }"` for anything they do not cover.
18
+ import { computed, useAttrs } from 'vue'
19
+
10
20
  defineProps<{
11
- /** Accessible name + tooltip. Required — the whole point of the primitive. */
21
+ /** Accessible name + tooltip. Required: the whole point of the primitive. */
12
22
  label: string
13
23
  }>()
14
24
  defineOptions({ inheritAttrs: false })
25
+
26
+ const attrs = useAttrs()
27
+ const buttonAttrs = computed(() => {
28
+ const { class: _class, style: _style, ...rest } = attrs
29
+ return rest
30
+ })
15
31
  </script>
16
32
 
17
33
  <template>
18
- <UButton v-bind="$attrs" :title="label" :aria-label="label" />
34
+ <UTooltip :text="label">
35
+ <!-- The trigger is a SPAN around the button, not the button. A disabled `<button>` fires no
36
+ pointer or focus event, so a tooltip bound straight to it never opens: the one state
37
+ where the hint is the only thing explaining why the control does nothing (a blocked
38
+ delete naming its reason) was exactly the state that lost it. The span is
39
+ `inline-flex` so it adds no box of its own. -->
40
+ <span class="inline-flex" :class="$attrs.class" :style="$attrs.style">
41
+ <UButton v-bind="buttonAttrs" :aria-label="label" />
42
+ </span>
43
+ </UTooltip>
19
44
  </template>
@@ -25,15 +25,15 @@ defineProps<{
25
25
  :data-testid="testid"
26
26
  >
27
27
  <UIcon name="i-lucide-loader-circle" class="h-5 w-5 animate-spin text-primary" />
28
- <p class="text-[13px] text-default">{{ title }}</p>
29
- <p class="text-[12px] text-muted">{{ hint }}</p>
28
+ <p class="text-sm text-default">{{ title }}</p>
29
+ <p class="text-xs text-muted">{{ hint }}</p>
30
30
  </div>
31
31
  <div
32
32
  v-else
33
33
  class="rounded-lg border border-app-error-900/60 bg-app-error-950/20 p-4 text-center"
34
34
  :data-testid="testid"
35
35
  >
36
- <p class="text-[13px] text-app-error-200">{{ title }}</p>
37
- <p class="mt-1 text-[12px] text-muted">{{ hint }}</p>
36
+ <p class="text-sm text-app-error-200">{{ title }}</p>
37
+ <p class="mt-1 text-xs text-muted">{{ hint }}</p>
38
38
  </div>
39
39
  </template>
@@ -55,7 +55,11 @@ const html = computed(() => renderMarkdown(props.text))
55
55
  font-style: italic;
56
56
  }
57
57
  .cf-prose :deep(code) {
58
- border-radius: 0.25rem;
58
+ /* `--ui-radius` rather than `--radius-sm`: Nuxt UI declares the `--radius-*` scale in a
59
+ * `@theme default inline` block, so Tailwind emits those only when the COMPILED stylesheet
60
+ * graph references them. This scoped block is not part of that compile, and `--ui-radius` is
61
+ * an ordinary custom property on `:root`. `--radius-sm` is `var(--ui-radius)` (prose.css). */
62
+ border-radius: var(--ui-radius);
59
63
  background: var(--ui-bg-elevated);
60
64
  padding: 0.1rem 0.3rem;
61
65
  font-family: var(--font-mono);
@@ -65,7 +69,9 @@ const html = computed(() => renderMarkdown(props.text))
65
69
  .cf-prose :deep(pre) {
66
70
  margin: 0.6rem 0;
67
71
  overflow: auto;
68
- border-radius: 0.5rem;
72
+ /* `--radius-lg` is `calc(var(--ui-radius) * 2)`; see the note on `code` above for why this
73
+ * block cannot name the scale variable. */
74
+ border-radius: calc(var(--ui-radius) * 2);
69
75
  background: var(--app-bg-canvas);
70
76
  padding: 0.75rem 0.9rem;
71
77
  }