@cat-factory/app 0.307.1 → 0.309.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 (255) hide show
  1. package/README.md +21 -0
  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 +3 -2
  6. package/app/components/auth/UserMenu.vue +1 -1
  7. package/app/components/binaryCandidates/BinaryCandidatesWindow.vue +15 -15
  8. package/app/components/binaryOutput/BinaryOutputReport.vue +10 -14
  9. package/app/components/binaryOutput/StoredAssetView.vue +5 -5
  10. package/app/components/board/AddTaskModal.vue +16 -14
  11. package/app/components/board/AgentFailureCard.vue +9 -9
  12. package/app/components/board/AgentFailureHistory.vue +1 -1
  13. package/app/components/board/CreateInitiativeModal.vue +2 -2
  14. package/app/components/board/FailureDetail.vue +1 -1
  15. package/app/components/board/FailureHistoryList.vue +5 -5
  16. package/app/components/board/RecurringPipelineModal.vue +12 -11
  17. package/app/components/board/ReviewFrictionDialog.vue +4 -3
  18. package/app/components/board/StepExecutionHistory.vue +8 -8
  19. package/app/components/board/nodes/AgentChip.vue +1 -1
  20. package/app/components/board/nodes/BlockNode.vue +8 -9
  21. package/app/components/board/nodes/EpicNode.vue +4 -4
  22. package/app/components/board/nodes/FrameSwimlanes.vue +4 -7
  23. package/app/components/board/nodes/InitiativeCard.vue +4 -4
  24. package/app/components/board/nodes/LaneGroup.vue +3 -5
  25. package/app/components/board/nodes/TaskCard.vue +8 -8
  26. package/app/components/board/nodes/TaskLane.vue +3 -3
  27. package/app/components/board/nodes/TaskPipelineMini.vue +13 -12
  28. package/app/components/bootstrap/AdoptionReviewModal.vue +7 -6
  29. package/app/components/bootstrap/BootstrapModal.vue +11 -10
  30. package/app/components/bootstrap/BootstrapRunSteps.vue +1 -1
  31. package/app/components/brainstorm/BrainstormWindow.vue +8 -7
  32. package/app/components/bugFishing/BugFishingWindow.vue +37 -36
  33. package/app/components/clarity/ClarityReviewWindow.vue +8 -7
  34. package/app/components/common/AsyncViewError.vue +1 -1
  35. package/app/components/common/ClarificationItem.vue +6 -6
  36. package/app/components/common/DescriptorFields.vue +1 -1
  37. package/app/components/common/EmptyState.vue +1 -1
  38. package/app/components/common/InterviewGateNotice.vue +4 -4
  39. package/app/components/common/MarkdownProse.vue +8 -2
  40. package/app/components/common/SectionLabel.vue +38 -0
  41. package/app/components/consensus/ConsensusSessionWindow.vue +10 -9
  42. package/app/components/context/ContextAttachmentFields.vue +9 -8
  43. package/app/components/docs/DocInterviewWindow.vue +10 -9
  44. package/app/components/documents/ContextDocumentPicker.vue +10 -12
  45. package/app/components/documents/DocumentImportModal.vue +3 -2
  46. package/app/components/documents/DocumentSourceConnectModal.vue +2 -2
  47. package/app/components/documents/DocumentSyncState.vue +1 -1
  48. package/app/components/documents/DocumentTemplatesModal.vue +5 -4
  49. package/app/components/documents/RepoContextDocPicker.vue +3 -6
  50. package/app/components/documents/StartFromDesignModal.vue +4 -4
  51. package/app/components/documents/TaskContextDocs.vue +1 -1
  52. package/app/components/environments/EnvSetupStepper.vue +3 -3
  53. package/app/components/environments/EnvironmentStatusPanel.vue +11 -10
  54. package/app/components/environments/steps/EnvPreflightStep.vue +8 -8
  55. package/app/components/environments/steps/EnvReviewStep.vue +23 -22
  56. package/app/components/environments/steps/EnvSaveStep.vue +5 -5
  57. package/app/components/focus/BlockFocusView.vue +11 -10
  58. package/app/components/followUp/FollowUpWindow.vue +10 -10
  59. package/app/components/forkDecision/ForkDecisionWindow.vue +23 -26
  60. package/app/components/foundational/FoundationalContractSummary.vue +2 -2
  61. package/app/components/foundational/FoundationalServiceCatalogList.vue +3 -3
  62. package/app/components/foundational/FoundationalServiceRegistry.vue +3 -3
  63. package/app/components/foundational/FoundationalSuppressions.vue +2 -2
  64. package/app/components/fragments/FragmentLibraryManager.vue +3 -3
  65. package/app/components/fragments/FragmentSelector.vue +10 -11
  66. package/app/components/gates/GateFailingCheckList.vue +3 -3
  67. package/app/components/gates/GateResultView.vue +39 -38
  68. package/app/components/github/AddServiceFromRepoModal.vue +5 -4
  69. package/app/components/github/BranchProtectionPreflight.vue +5 -5
  70. package/app/components/github/GitHubConnect.vue +5 -3
  71. package/app/components/github/GitHubPanel.vue +14 -13
  72. package/app/components/github/RepoTreeBrowser.vue +1 -1
  73. package/app/components/humanTest/HumanTestWindow.vue +18 -17
  74. package/app/components/initiative/InitiativePlanNotice.vue +3 -3
  75. package/app/components/initiative/InitiativePlanReview.vue +19 -21
  76. package/app/components/initiative/InitiativePlanningWindow.vue +5 -5
  77. package/app/components/initiative/InitiativeTrackerWindow.vue +54 -53
  78. package/app/components/judge/JudgeResultView.vue +24 -25
  79. package/app/components/kaizen/KaizenPanel.vue +4 -4
  80. package/app/components/kaizen/KaizenStepStatus.vue +7 -6
  81. package/app/components/layout/AccountAuditLog.vue +1 -1
  82. package/app/components/layout/AccountDeploymentSettings.vue +10 -10
  83. package/app/components/layout/AccountFailureKindRules.vue +13 -13
  84. package/app/components/layout/AccountFoundationalSettings.vue +1 -1
  85. package/app/components/layout/AccountFragmentSettings.vue +1 -1
  86. package/app/components/layout/AccountModelPolicySettings.vue +7 -7
  87. package/app/components/layout/AccountPlatformAlertSettings.vue +8 -8
  88. package/app/components/layout/AccountRiskPolicySettings.vue +2 -2
  89. package/app/components/layout/AccountRunCredentialSettings.vue +3 -3
  90. package/app/components/layout/AccountSkillSettings.vue +1 -1
  91. package/app/components/layout/AccountTeamSettings.vue +4 -0
  92. package/app/components/layout/AiProvidersBanner.vue +1 -1
  93. package/app/components/layout/AppearanceSwitcher.vue +1 -1
  94. package/app/components/layout/BoardSwitcher.vue +3 -2
  95. package/app/components/layout/BoardToolbar.vue +6 -2
  96. package/app/components/layout/CommandBar.vue +4 -2
  97. package/app/components/layout/DefaultTestEnvBanner.vue +2 -2
  98. package/app/components/layout/IntegrationsHub.vue +4 -3
  99. package/app/components/layout/LanguageSwitcher.vue +1 -1
  100. package/app/components/layout/ModelProvidersHub.vue +6 -4
  101. package/app/components/layout/NotificationsInbox.vue +8 -7
  102. package/app/components/layout/PersonalSetupModal.vue +5 -3
  103. package/app/components/layout/RolePrompt.vue +1 -1
  104. package/app/components/layout/SideBar.vue +3 -5
  105. package/app/components/layout/SpendWarningBanner.vue +3 -3
  106. package/app/components/layout/TranslationWarningBanner.vue +1 -1
  107. package/app/components/layout/UiModeSwitcher.vue +7 -6
  108. package/app/components/layout/UiRoleSwitcher.vue +2 -2
  109. package/app/components/media/ArtifactLightbox.vue +6 -6
  110. package/app/components/media/ImageCompare.vue +16 -16
  111. package/app/components/merge/MergeEffortChips.vue +4 -6
  112. package/app/components/notifications/NotificationSettingsPanel.vue +6 -5
  113. package/app/components/observability/OutcomeFilterChips.vue +1 -1
  114. package/app/components/observability/RunFailureSummary.vue +14 -17
  115. package/app/components/observability/StepMetricsBar.vue +9 -8
  116. package/app/components/observability/StepModelActivity.vue +4 -3
  117. package/app/components/observability/ToolCallList.vue +15 -18
  118. package/app/components/outcome/OutcomeSummaryWindow.vue +43 -42
  119. package/app/components/palettes/AgentPalette.vue +7 -5
  120. package/app/components/palettes/AgentTierSelect.vue +1 -1
  121. package/app/components/palettes/PipelinePurposeSelect.vue +2 -1
  122. package/app/components/panels/AgentStepDetail.vue +27 -30
  123. package/app/components/panels/AttemptEntryHeader.vue +2 -2
  124. package/app/components/panels/DecisionModal.vue +1 -1
  125. package/app/components/panels/GenericStructuredResultView.vue +6 -5
  126. package/app/components/panels/InspectorPanel.vue +15 -13
  127. package/app/components/panels/MergerResultView.vue +10 -9
  128. package/app/components/panels/ObservabilityPanel.vue +66 -76
  129. package/app/components/panels/OperatorDashboardPanel.vue +27 -26
  130. package/app/components/panels/ReportsPanel.vue +26 -25
  131. package/app/components/panels/ReportsSpendBreakdown.vue +2 -2
  132. package/app/components/panels/ResultWindowShell.vue +19 -18
  133. package/app/components/panels/RunDetailLoadState.vue +1 -1
  134. package/app/components/panels/StepContainerStatus.vue +8 -7
  135. package/app/components/panels/StepDelegatedStatus.vue +13 -12
  136. package/app/components/panels/StepEffortReport.vue +12 -17
  137. package/app/components/panels/StepFragmentAdherence.vue +9 -10
  138. package/app/components/panels/StepMetadataCard.vue +41 -43
  139. package/app/components/panels/StepReproductionReport.vue +18 -20
  140. package/app/components/panels/StepRunMeta.vue +27 -26
  141. package/app/components/panels/StepTestReport.vue +16 -15
  142. package/app/components/panels/StepToolServers.vue +7 -8
  143. package/app/components/panels/StepValidationReport.vue +5 -5
  144. package/app/components/panels/inspector/ContainerSummary.vue +4 -4
  145. package/app/components/panels/inspector/DocReferenceRepos.vue +5 -4
  146. package/app/components/panels/inspector/EnvProbeReport.vue +16 -13
  147. package/app/components/panels/inspector/EpicChildren.vue +8 -7
  148. package/app/components/panels/inspector/FrontendBindingsResolved.vue +5 -4
  149. package/app/components/panels/inspector/FrontendConfig.vue +26 -34
  150. package/app/components/panels/inspector/InitiativeInspector.vue +3 -3
  151. package/app/components/panels/inspector/InspectorSection.vue +7 -4
  152. package/app/components/panels/inspector/RecurringScheduleSettings.vue +9 -8
  153. package/app/components/panels/inspector/ServiceConnections.vue +2 -2
  154. package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +2 -2
  155. package/app/components/panels/inspector/ServiceSelfTests.vue +9 -13
  156. package/app/components/panels/inspector/ServiceTestConfig.vue +41 -45
  157. package/app/components/panels/inspector/ServiceTestSecrets.vue +3 -3
  158. package/app/components/panels/inspector/ServiceTestingContext.vue +2 -2
  159. package/app/components/panels/inspector/ServiceValidationConfig.vue +2 -2
  160. package/app/components/panels/inspector/TaskAgentConfig.vue +2 -2
  161. package/app/components/panels/inspector/TaskAprioriBranches.vue +6 -5
  162. package/app/components/panels/inspector/TaskDependencies.vue +1 -1
  163. package/app/components/panels/inspector/TaskEstimateBadge.vue +3 -7
  164. package/app/components/panels/inspector/TaskExecution.vue +13 -12
  165. package/app/components/panels/inspector/TaskRunSettings.vue +35 -38
  166. package/app/components/panels/inspector/TaskStructure.vue +5 -4
  167. package/app/components/pipeline/AgentPromptEditor.vue +15 -14
  168. package/app/components/pipeline/BinaryOutputStepPicker.vue +42 -52
  169. package/app/components/pipeline/EstimateThresholdFields.vue +2 -2
  170. package/app/components/pipeline/GateConfigFields.vue +6 -6
  171. package/app/components/pipeline/IterationCapPrompt.vue +1 -1
  172. package/app/components/pipeline/PipelineBuilder.vue +28 -27
  173. package/app/components/pipeline/PipelineHealthModal.vue +7 -7
  174. package/app/components/pipeline/PipelinePicker.vue +3 -3
  175. package/app/components/pipeline/PipelinePreview.vue +6 -7
  176. package/app/components/pipeline/PipelineProgress.vue +23 -24
  177. package/app/components/prReview/PrReviewWindow.vue +42 -41
  178. package/app/components/providers/AiPresetMismatchDialog.vue +4 -3
  179. package/app/components/providers/AiProviderOnboardingModal.vue +2 -2
  180. package/app/components/providers/ApiKeysSection.vue +7 -6
  181. package/app/components/providers/PersonalSubscriptionSection.vue +4 -3
  182. package/app/components/providers/VendorCredentialsModal.vue +5 -4
  183. package/app/components/provisioning/ProvisioningLogsDrawer.vue +10 -9
  184. package/app/components/ralph/RalphLoopResultView.vue +25 -26
  185. package/app/components/requirements/RequirementsReviewWindow.vue +13 -12
  186. package/app/components/riskPolicy/RiskPolicyPicker.vue +5 -5
  187. package/app/components/riskPolicy/RiskPolicyPreview.vue +16 -15
  188. package/app/components/sandbox/SandboxPanel.vue +21 -20
  189. package/app/components/settings/ApiTokensPanel.vue +1 -1
  190. package/app/components/settings/BudgetSettings.vue +18 -17
  191. package/app/components/settings/CapabilityCredentialsPanel.vue +5 -5
  192. package/app/components/settings/CloudflareHandlerSection.vue +16 -16
  193. package/app/components/settings/ComposeEnvironmentSetupSection.vue +14 -12
  194. package/app/components/settings/ConnectionTestVerdict.vue +1 -1
  195. package/app/components/settings/ConsensusGroupsSection.vue +21 -20
  196. package/app/components/settings/CustomManifestTypeEditor.vue +8 -7
  197. package/app/components/settings/DefaultProvisionTypeSection.vue +5 -5
  198. package/app/components/settings/InfraHandlersConfigurator.vue +14 -15
  199. package/app/components/settings/InfrastructureBackendPicker.vue +2 -2
  200. package/app/components/settings/IssueTrackerPanel.vue +9 -15
  201. package/app/components/settings/KubernetesEngineForm.vue +9 -8
  202. package/app/components/settings/KubernetesEnvironmentForm.vue +4 -3
  203. package/app/components/settings/LocalContainerPoolSettings.vue +2 -2
  204. package/app/components/settings/LocalModelEndpointsPanel.vue +10 -9
  205. package/app/components/settings/MergeClassRulesEditor.vue +6 -5
  206. package/app/components/settings/MergeRolePolicyEditor.vue +13 -12
  207. package/app/components/settings/ModelConfigurationPanel.vue +8 -11
  208. package/app/components/settings/ModelPresetHealthModal.vue +3 -3
  209. package/app/components/settings/ObservabilityConnectionPanel.vue +1 -1
  210. package/app/components/settings/OpenRouterCatalogPanel.vue +6 -5
  211. package/app/components/settings/PackageRegistriesPanel.vue +4 -4
  212. package/app/components/settings/ProviderConnectionTab.vue +5 -4
  213. package/app/components/settings/ProviderManifestEditor.vue +9 -8
  214. package/app/components/settings/ProviderPreferenceEditor.vue +8 -7
  215. package/app/components/settings/RiskPolicyCreateForm.vue +40 -26
  216. package/app/components/settings/RiskPolicyEditorRow.vue +18 -17
  217. package/app/components/settings/RiskPolicyHealthModal.vue +3 -3
  218. package/app/components/settings/RiskPolicyPanel.vue +11 -13
  219. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +5 -4
  220. package/app/components/settings/SharedStacksPanel.vue +5 -5
  221. package/app/components/settings/TaskSourceCard.vue +2 -2
  222. package/app/components/settings/TaskTypeSuppressionsPanel.vue +4 -4
  223. package/app/components/settings/ToolServerChecklist.vue +22 -22
  224. package/app/components/settings/UsageSettings.vue +13 -13
  225. package/app/components/settings/UserSecretsSection.vue +4 -3
  226. package/app/components/settings/WorkspaceMetadataSettings.vue +6 -5
  227. package/app/components/settings/WorkspaceSettingsPanel.vue +72 -58
  228. package/app/components/skills/ReviewSkillQueue.vue +8 -7
  229. package/app/components/skills/SkillLibraryManager.vue +2 -2
  230. package/app/components/slack/SlackPanel.vue +8 -7
  231. package/app/components/spec/ServiceSpecWindow.vue +15 -16
  232. package/app/components/tasks/BugHuntModal.vue +1 -1
  233. package/app/components/tasks/ContextIssuePicker.vue +4 -6
  234. package/app/components/tasks/TaskContextIssues.vue +1 -1
  235. package/app/components/tasks/TaskImportModal.vue +3 -2
  236. package/app/components/tasks/TaskSourceConnectModal.vue +5 -5
  237. package/app/components/testing/TestReportWindow.vue +54 -53
  238. package/app/components/tutorial/TutorialNudge.vue +3 -2
  239. package/app/components/tutorial/TutorialOverlay.vue +3 -2
  240. package/app/components/vcs/GitLabConnect.vue +1 -1
  241. package/app/components/visualConfirm/VisualConfirmationWindow.vue +23 -25
  242. package/app/composables/usePipelineErrorToast.ts +2 -0
  243. package/app/utils/catalog.ts +8 -0
  244. package/app/utils/theme/builtins.ts +7 -1
  245. package/i18n/locales/de.json +6 -3
  246. package/i18n/locales/en.json +6 -3
  247. package/i18n/locales/es.json +6 -3
  248. package/i18n/locales/fr.json +6 -3
  249. package/i18n/locales/he.json +6 -3
  250. package/i18n/locales/it.json +6 -3
  251. package/i18n/locales/ja.json +6 -3
  252. package/i18n/locales/pl.json +6 -3
  253. package/i18n/locales/tr.json +6 -3
  254. package/i18n/locales/uk.json +6 -3
  255. package/package.json +2 -2
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { DropdownMenuItem } from '@nuxt/ui'
3
3
  import type { CloudProvider } from '~/types/domain'
4
+ import SectionLabel from '~/components/common/SectionLabel.vue'
4
5
 
5
6
  // Account + board switching. Picks the active account (personal / org) and the
6
7
  // active board within it, and manages boards (new / rename / delete). The account
@@ -265,9 +266,9 @@ async function submitPrompt() {
265
266
  :name="accounts.activeAccount?.type === 'org' ? 'i-lucide-users' : 'i-lucide-user'"
266
267
  class="h-3.5 w-3.5 shrink-0 text-muted"
267
268
  />
268
- <span class="truncate text-[11px] font-medium uppercase tracking-wide text-muted">
269
+ <SectionLabel as="span" class="truncate">
269
270
  {{ accounts.activeAccount?.name ?? t('layout.boardSwitcher.accountFallback') }}
270
- </span>
271
+ </SectionLabel>
271
272
  <UIcon name="i-lucide-chevrons-up-down" class="ms-auto h-3.5 w-3.5 shrink-0 text-app-600" />
272
273
  </button>
273
274
  </UDropdownMenu>
@@ -153,14 +153,18 @@ const decisionItems = computed(() =>
153
153
  drops on narrow viewports) so the zoom level is never a mystery. -->
154
154
  <button
155
155
  type="button"
156
- class="w-16 rounded text-center text-xs tabular-nums text-toned hover:bg-elevated focus-visible:ring-2 focus-visible:ring-app-400/60 sm:w-20"
156
+ class="w-16 rounded-sm text-center text-xs tabular-nums text-toned hover:bg-elevated focus-visible:ring-2 focus-visible:ring-app-400/60 sm:w-20"
157
157
  :title="t('board.toolbar.resetZoom')"
158
158
  :aria-label="t('board.toolbar.resetZoom')"
159
159
  data-testid="board-zoom-reset"
160
160
  @click="resetZoom()"
161
161
  >
162
162
  {{ zoomPct }}%
163
- <span class="hidden text-[9px] uppercase tracking-wide text-dimmed sm:block">{{
163
+ <!-- The zoom band this readout is currently in, under the percentage: a second VALUE
164
+ annotating the first, not a heading over what follows, so it is not a
165
+ `common/SectionLabel.vue`. It stays a step smaller and unweighted so it does not compete
166
+ with the percentage inside a `w-20` button. -->
167
+ <span class="hidden text-3xs uppercase tracking-wide text-dimmed sm:block">{{
164
168
  lodLabel
165
169
  }}</span>
166
170
  </button>
@@ -6,6 +6,8 @@
6
6
  // from the add-task flow.) Commands are assembled from the live stores so only
7
7
  // available actions (connected integrations, etc.) show.
8
8
 
9
+ import SectionLabel from '~/components/common/SectionLabel.vue'
10
+
9
11
  interface Command {
10
12
  id: string
11
13
  label: string
@@ -276,9 +278,9 @@ function indexOf(cmd: Command) {
276
278
  </p>
277
279
 
278
280
  <div v-for="group in groups" :key="group.name" class="mb-1">
279
- <p class="px-2 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-wide text-dimmed">
281
+ <SectionLabel as="p" class="px-2 pb-1 pt-2">
280
282
  {{ group.name }}
281
- </p>
283
+ </SectionLabel>
282
284
  <button
283
285
  v-for="cmd in group.items"
284
286
  :key="cmd.id"
@@ -74,7 +74,7 @@ function openConfig(event: MouseEvent) {
74
74
  @click="ui.dismissDefaultProvision()"
75
75
  />
76
76
  </div>
77
- <p class="mt-1 text-[13px] text-app-info-200/90">
77
+ <p class="mt-1 text-sm text-app-info-200/90">
78
78
  {{ t('layout.defaultTestEnvBanner.body') }}
79
79
  </p>
80
80
  <div class="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2">
@@ -92,7 +92,7 @@ function openConfig(event: MouseEvent) {
92
92
  <!-- The URL itself, shown so it can be read and copied, not just clicked. -->
93
93
  <a
94
94
  :href="configUrl"
95
- class="min-w-0 truncate font-mono text-[11px] text-app-info-300/80 underline decoration-dotted underline-offset-2 hover:text-app-info-200"
95
+ class="min-w-0 truncate font-mono text-2xs text-app-info-300/80 underline decoration-dotted underline-offset-2 hover:text-app-info-200"
96
96
  data-testid="default-test-env-url"
97
97
  @click="openConfig"
98
98
  >
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { showOverrideField } from '~/utils/uiMode'
3
+ import SectionLabel from '~/components/common/SectionLabel.vue'
3
4
 
4
5
  // The Integrations hub: a single modal that lists the OPTIONAL external systems the WORKSPACE
5
6
  // can enable or link in — the ones that feed a run its context (source control, documents,
@@ -422,9 +423,9 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
422
423
  </p>
423
424
 
424
425
  <section v-for="group in filteredGroups" :key="group.title">
425
- <h3 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-muted">
426
+ <SectionLabel as="h3" class="mb-2 px-1">
426
427
  {{ group.title }}
427
- </h3>
428
+ </SectionLabel>
428
429
  <div class="space-y-1.5">
429
430
  <button
430
431
  v-for="item in group.items"
@@ -443,7 +444,7 @@ const filteredGroups = computed<IntegrationGroup[]>(() => {
443
444
  <UBadge v-else-if="item.attention" color="warning" variant="subtle" size="sm">
444
445
  {{ item.attentionLabel || t('layout.integrationsHub.status.needsAttention') }}
445
446
  </UBadge>
446
- <span v-else class="text-[11px] text-dimmed">{{
447
+ <span v-else class="text-2xs text-dimmed">{{
447
448
  t('layout.integrationsHub.status.notConnected')
448
449
  }}</span>
449
450
  <UBadge
@@ -50,7 +50,7 @@ const items = computed<DropdownMenuItem[][]>(() => [
50
50
  >
51
51
  <UIcon name="i-lucide-languages" class="h-4 w-4 shrink-0 text-muted" />
52
52
  <div v-if="!collapsed" class="min-w-0 flex-1">
53
- <div class="truncate text-[10px] uppercase tracking-wide text-dimmed">
53
+ <div class="truncate text-2xs uppercase tracking-wide text-muted">
54
54
  {{ t('language.switcher') }}
55
55
  </div>
56
56
  <div class="truncate text-xs font-medium text-highlighted">{{ current }}</div>
@@ -12,6 +12,8 @@
12
12
  // `ui.openFromModelProviders(...)`, so opening one closes this hub and gives that panel a
13
13
  // "Back to Model providers" control (IntegrationBackTitle). Layout mirrors the Integrations
14
14
  // hub row-for-row so the two read as siblings.
15
+ import SectionLabel from '~/components/common/SectionLabel.vue'
16
+
15
17
  const { t } = useI18n()
16
18
  const ui = useUiStore()
17
19
  const apiKeys = useApiKeysStore()
@@ -200,9 +202,9 @@ const filteredGroups = computed<ProviderGroup[]>(() => {
200
202
  </p>
201
203
 
202
204
  <section v-for="group in filteredGroups" :key="group.title">
203
- <h3 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-muted">
205
+ <SectionLabel as="h3" class="mb-2 px-1">
204
206
  {{ group.title }}
205
- </h3>
207
+ </SectionLabel>
206
208
  <div class="space-y-1.5">
207
209
  <button
208
210
  v-for="item in group.items"
@@ -219,7 +221,7 @@ const filteredGroups = computed<ProviderGroup[]>(() => {
219
221
  <UBadge v-if="item.connected" color="success" variant="subtle" size="sm">
220
222
  {{ item.status || t('layout.modelProvidersHub.status.connected') }}
221
223
  </UBadge>
222
- <span v-else class="text-[11px] text-dimmed">{{
224
+ <span v-else class="text-2xs text-dimmed">{{
223
225
  t('layout.modelProvidersHub.status.notConnected')
224
226
  }}</span>
225
227
  <UBadge
@@ -239,7 +241,7 @@ const filteredGroups = computed<ProviderGroup[]>(() => {
239
241
  />
240
242
  </button>
241
243
  </div>
242
- <p v-if="group.note" class="mt-1.5 px-1 text-[11px] text-dimmed">{{ group.note }}</p>
244
+ <p v-if="group.note" class="mt-1.5 px-1 text-2xs text-dimmed">{{ group.note }}</p>
243
245
  </section>
244
246
  </div>
245
247
  </template>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { Notification } from '~/types/domain'
3
3
  import type { ReviewEffort } from '~/types/merge'
4
+ import SectionLabel from '~/components/common/SectionLabel.vue'
4
5
 
5
6
  // The board's notification inbox: a bell with an open-count badge that opens a
6
7
  // panel of human-actionable items (a PR awaiting a merge decision, a completed
@@ -406,9 +407,9 @@ function revealDecision(n: Notification) {
406
407
 
407
408
  <template #content>
408
409
  <div class="max-h-[28rem] w-[min(24rem,92vw)] overflow-y-auto p-2">
409
- <div class="px-2 py-1 text-[11px] font-semibold uppercase tracking-wide text-muted">
410
+ <SectionLabel class="px-2 py-1">
410
411
  {{ t('layout.notifications.heading') }}
411
- </div>
412
+ </SectionLabel>
412
413
  <div
413
414
  v-for="n in notifications.open"
414
415
  :key="n.id"
@@ -437,18 +438,18 @@ function revealDecision(n: Notification) {
437
438
  </button>
438
439
  <span
439
440
  v-if="isUrgent(n)"
440
- class="shrink-0 rounded bg-app-error-500/20 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-app-error-400"
441
+ class="shrink-0 rounded-sm bg-app-error-500/20 px-1.5 py-0.5 text-3xs font-semibold uppercase tracking-wide text-app-error-400"
441
442
  >
442
443
  {{ t('layout.notifications.overdue') }}
443
444
  </span>
444
445
  </div>
445
- <p class="mt-0.5 text-[11px] leading-snug text-muted">{{ n.body }}</p>
446
+ <p class="mt-0.5 text-2xs leading-snug text-muted">{{ n.body }}</p>
446
447
  <a
447
448
  v-if="n.payload?.prUrl"
448
449
  :href="n.payload.prUrl"
449
450
  target="_blank"
450
451
  rel="noopener"
451
- class="mt-1 inline-flex items-center gap-1 text-[11px] text-app-info-400 hover:underline"
452
+ class="mt-1 inline-flex items-center gap-1 text-2xs text-app-info-400 hover:underline"
452
453
  >
453
454
  <UIcon name="i-lucide-external-link" class="h-3 w-3" />
454
455
  {{ t('layout.notifications.openPr') }}
@@ -467,7 +468,7 @@ function revealDecision(n: Notification) {
467
468
  v-for="run in failingRuns(n)"
468
469
  :key="run.executionId"
469
470
  :type="canOpenFailingRun(run) ? 'button' : undefined"
470
- class="flex items-center gap-1 text-start text-[11px]"
471
+ class="flex items-center gap-1 text-start text-2xs"
471
472
  :class="
472
473
  canOpenFailingRun(run)
473
474
  ? 'text-app-info-400 hover:underline'
@@ -486,7 +487,7 @@ function revealDecision(n: Notification) {
486
487
  })
487
488
  }}</span>
488
489
  </component>
489
- <span v-if="failingRunsOmitted(n) > 0" class="text-[11px] text-dimmed">
490
+ <span v-if="failingRunsOmitted(n) > 0" class="text-2xs text-dimmed">
490
491
  {{ t('layout.notifications.failingRunsMore', { count: failingRunsOmitted(n) }) }}
491
492
  </span>
492
493
  </div>
@@ -5,6 +5,8 @@
5
5
  // previously sat, confusingly, among the workspace-wide integrations. Each row reuses the
6
6
  // existing per-panel handlers via `ui.openFromPersonal(...)`, so opening one closes this
7
7
  // hub, reveals that panel, and gives it a "Back to My setup" control (IntegrationBackTitle).
8
+ import SectionLabel from '~/components/common/SectionLabel.vue'
9
+
8
10
  const { t } = useI18n()
9
11
  const ui = useUiStore()
10
12
  const userSecrets = useUserSecretsStore()
@@ -117,9 +119,9 @@ const groups = computed<PersonalGroup[]>(() => {
117
119
  </p>
118
120
 
119
121
  <section v-for="group in groups" :key="group.title">
120
- <h3 class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-muted">
122
+ <SectionLabel as="h3" class="mb-2 px-1">
121
123
  {{ group.title }}
122
- </h3>
124
+ </SectionLabel>
123
125
  <div class="space-y-1.5">
124
126
  <button
125
127
  v-for="item in group.items"
@@ -135,7 +137,7 @@ const groups = computed<PersonalGroup[]>(() => {
135
137
  <UBadge v-if="item.connected" color="success" variant="subtle" size="sm">
136
138
  {{ item.status || t('layout.personalSetup.connected') }}
137
139
  </UBadge>
138
- <span v-else class="text-[11px] text-dimmed">
140
+ <span v-else class="text-2xs text-dimmed">
139
141
  {{ t('layout.personalSetup.notConnected') }}
140
142
  </span>
141
143
  </div>
@@ -56,7 +56,7 @@ function pick(role: UiRole) {
56
56
  </div>
57
57
  <!-- The choice is not a commitment, and saying so is what makes the narrowed role
58
58
  pickable: it is one dropdown at the top of the sidebar to leave again. -->
59
- <p class="text-[11px] leading-snug text-dimmed">{{ t('uiRole.prompt.change') }}</p>
59
+ <p class="text-2xs leading-snug text-dimmed">{{ t('uiRole.prompt.change') }}</p>
60
60
  </div>
61
61
  </template>
62
62
  <template #footer>
@@ -20,6 +20,7 @@ import UiRoleSwitcher from '~/components/layout/UiRoleSwitcher.vue'
20
20
  import UserMenu from '~/components/auth/UserMenu.vue'
21
21
  import { useViewport } from '~/composables/useViewport'
22
22
  import type { NavContribution } from '~/modular/nav-contributions'
23
+ import SectionLabel from '~/components/common/SectionLabel.vue'
23
24
 
24
25
  const { t } = useI18n()
25
26
 
@@ -248,12 +249,9 @@ watch(
248
249
  <template v-for="section in sidebarGroups" :key="section.group">
249
250
  <USeparator />
250
251
  <section>
251
- <h2
252
- v-if="!railed"
253
- class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-muted"
254
- >
252
+ <SectionLabel v-if="!railed" as="h2" class="mb-2 px-1">
255
253
  {{ t(section.labelKey) }}
256
- </h2>
254
+ </SectionLabel>
257
255
  <div class="space-y-1.5">
258
256
  <UButton
259
257
  v-for="item in section.items"
@@ -54,7 +54,7 @@ async function resume() {
54
54
 
55
55
  <dl class="mt-4 grid grid-cols-2 gap-3 sm:grid-cols-3">
56
56
  <div class="rounded-lg bg-app-error-900/50 px-3 py-2">
57
- <dt class="text-[11px] uppercase tracking-wide text-app-error-300/80">
57
+ <dt class="text-2xs uppercase tracking-wide text-app-error-300/80">
58
58
  {{ t('layout.spendWarningBanner.spent') }}
59
59
  </dt>
60
60
  <dd class="text-base font-semibold tabular-nums text-app-error-50">
@@ -62,7 +62,7 @@ async function resume() {
62
62
  </dd>
63
63
  </div>
64
64
  <div class="rounded-lg bg-app-error-900/50 px-3 py-2">
65
- <dt class="text-[11px] uppercase tracking-wide text-app-error-300/80">
65
+ <dt class="text-2xs uppercase tracking-wide text-app-error-300/80">
66
66
  {{ t('layout.spendWarningBanner.budget') }}
67
67
  </dt>
68
68
  <dd class="text-base font-semibold tabular-nums text-app-error-50">
@@ -70,7 +70,7 @@ async function resume() {
70
70
  </dd>
71
71
  </div>
72
72
  <div class="rounded-lg bg-app-error-900/50 px-3 py-2">
73
- <dt class="text-[11px] uppercase tracking-wide text-app-error-300/80">
73
+ <dt class="text-2xs uppercase tracking-wide text-app-error-300/80">
74
74
  {{ t('layout.spendWarningBanner.tokens') }}
75
75
  </dt>
76
76
  <dd class="text-base font-semibold tabular-nums text-app-error-50">{{ tokens }}</dd>
@@ -31,7 +31,7 @@ function dismiss() {
31
31
  v-if="show"
32
32
  data-testid="translation-warning"
33
33
  role="alert"
34
- class="flex shrink-0 items-center gap-3 border-b border-app-warning-500/40 bg-app-warning-950/95 px-4 py-2 text-[13px] text-app-warning-100 shadow-lg backdrop-blur"
34
+ class="flex shrink-0 items-center gap-3 border-b border-app-warning-500/40 bg-app-warning-950/95 px-4 py-2 text-sm text-app-warning-100 shadow-lg backdrop-blur"
35
35
  >
36
36
  <UIcon name="i-lucide-languages" class="h-4 w-4 shrink-0 text-app-warning-400" />
37
37
  <p class="min-w-0 flex-1">
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
3
  import { UI_MODES, type UiMode } from '~/utils/uiMode'
4
+ import SectionLabel from '~/components/common/SectionLabel.vue'
4
5
 
5
6
  // Interface-tier picker, shown at the TOP of the sidebar under the board switcher. Basic mode
6
7
  // hides the power-user destinations and the less-used run options; advanced shows everything.
@@ -55,9 +56,9 @@ const pinnedTitle = computed(
55
56
  >
56
57
  <UIcon :name="icon" class="h-4 w-4 shrink-0 text-dimmed" />
57
58
  <div v-if="!collapsed" class="min-w-0 flex-1">
58
- <div class="truncate text-[10px] uppercase tracking-wide text-dimmed">
59
+ <SectionLabel class="truncate">
59
60
  {{ t('uiMode.switcher') }}
60
- </div>
61
+ </SectionLabel>
61
62
  <div class="truncate text-xs font-medium text-toned">{{ currentLabel }}</div>
62
63
  </div>
63
64
  <UIcon v-if="!collapsed" name="i-lucide-lock" class="h-3.5 w-3.5 shrink-0 text-app-600" />
@@ -76,15 +77,15 @@ const pinnedTitle = computed(
76
77
  @click="uiMode.toggleMode()"
77
78
  >
78
79
  <UIcon :name="icon" class="h-4 w-4 shrink-0 text-primary" />
79
- <span class="w-full truncate text-center text-[9px] font-medium uppercase text-toned">
80
+ <span class="w-full truncate text-center text-3xs font-medium uppercase text-toned">
80
81
  {{ currentLabel }}
81
82
  </span>
82
83
  </button>
83
84
 
84
85
  <div v-else data-testid="ui-mode-switcher" class="w-full">
85
- <div class="mb-1 px-1 text-[10px] uppercase tracking-wide text-dimmed">
86
+ <SectionLabel class="mb-1 px-1">
86
87
  {{ t('uiMode.switcher') }}
87
- </div>
88
+ </SectionLabel>
88
89
  <div
89
90
  role="group"
90
91
  :aria-label="t('uiMode.switcher')"
@@ -111,7 +112,7 @@ const pinnedTitle = computed(
111
112
 
112
113
  <!-- The one-line "what this tier gives you", so the choice is self-explanatory. Dropped in
113
114
  the collapsed rail, where the tooltip above carries the mode instead. -->
114
- <p v-if="!collapsed" class="px-1 text-[10px] leading-snug text-dimmed">
115
+ <p v-if="!collapsed" class="px-1 text-3xs leading-snug text-dimmed">
115
116
  {{ t(MODE_HINTS[uiMode.mode]) }}
116
117
  </p>
117
118
  </template>
@@ -47,7 +47,7 @@ const items = computed(() =>
47
47
  class="flex w-full flex-col items-center gap-0.5 rounded-lg border border-muted bg-default/60 px-1 py-1.5 transition hover:border-primary/60 hover:bg-elevated/60"
48
48
  >
49
49
  <UIcon :name="current.icon" class="h-4 w-4 shrink-0 text-primary" />
50
- <span class="w-full truncate text-center text-[9px] font-medium uppercase text-toned">
50
+ <span class="w-full truncate text-center text-3xs font-medium uppercase text-toned">
51
51
  {{ currentLabel }}
52
52
  </span>
53
53
  </button>
@@ -62,7 +62,7 @@ const items = computed(() =>
62
62
  >
63
63
  <UIcon :name="current.icon" class="h-4 w-4 shrink-0 text-primary" />
64
64
  <div class="min-w-0 flex-1">
65
- <div class="truncate text-[10px] uppercase tracking-wide text-dimmed">
65
+ <div class="truncate text-2xs uppercase tracking-wide text-muted">
66
66
  {{ t('uiRole.switcher') }}
67
67
  </div>
68
68
  <div class="truncate text-xs font-medium text-default">{{ currentLabel }}</div>
@@ -173,10 +173,10 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
173
173
  >
174
174
  <!-- Toolbar -->
175
175
  <div class="flex items-center gap-3 border-b border-default/60 px-4 py-2.5">
176
- <span class="min-w-0 flex-1 truncate text-[13px] font-medium text-default">
176
+ <span class="min-w-0 flex-1 truncate text-sm font-medium text-default">
177
177
  {{ current?.label ?? t('media.lightbox.fallbackTitle') }}
178
178
  </span>
179
- <span v-if="total > 1" class="shrink-0 text-[12px] tabular-nums text-muted">
179
+ <span v-if="total > 1" class="shrink-0 text-xs tabular-nums text-muted">
180
180
  {{ t('media.lightbox.counter', { current: index + 1, total }) }}
181
181
  </span>
182
182
  <div class="flex shrink-0 items-center gap-1">
@@ -188,7 +188,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
188
188
  >
189
189
  <UIcon name="i-lucide-zoom-out" class="h-4 w-4" />
190
190
  </button>
191
- <span class="w-10 text-center text-[11px] tabular-nums text-dimmed">{{
191
+ <span class="w-10 text-center text-2xs tabular-nums text-dimmed">{{
192
192
  n(scale, 'percent')
193
193
  }}</span>
194
194
  <button
@@ -236,7 +236,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
236
236
  :src="url"
237
237
  :alt="current?.alt ?? ''"
238
238
  draggable="false"
239
- class="max-h-full max-w-full select-none rounded shadow-2xl"
239
+ class="max-h-full max-w-full select-none rounded-sm shadow-2xl"
240
240
  :class="[
241
241
  scale > 1 ? (dragging ? 'cursor-grabbing' : 'cursor-grab') : 'cursor-zoom-in',
242
242
  dragging ? '' : 'transition-transform duration-100',
@@ -254,12 +254,12 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
254
254
  class="h-8 w-8"
255
255
  :class="state === 'error' ? '' : 'animate-spin'"
256
256
  />
257
- <p class="text-[12px]">
257
+ <p class="text-xs">
258
258
  {{ state === 'error' ? t('media.lightbox.failed') : t('media.lightbox.loading') }}
259
259
  </p>
260
260
  <button
261
261
  v-if="state === 'error' && current"
262
- class="text-[12px] text-app-warning-300 hover:underline"
262
+ class="text-xs text-app-warning-300 hover:underline"
263
263
  @click="props.blobs.retry(current.artifactId)"
264
264
  >
265
265
  {{ t('common.retry') }}
@@ -88,7 +88,7 @@ const diffCanvas = ref<HTMLCanvasElement | null>(null)
88
88
  // there reads as "no difference", the same zero the difference composite produces, in either colour
89
89
  // mode, so no mode-following token fits. Bound as a const so the guard's `fixed-colour-ok:` marker
90
90
  // stays on the class string; the inline-template form gets reflowed off its own line by oxfmt.
91
- const DIFF_CANVAS_CLASS = 'w-full rounded border border-default bg-black' // fixed-colour-ok: diff canvas backdrop
91
+ const DIFF_CANVAS_CLASS = 'w-full rounded-sm border border-default bg-black' // fixed-colour-ok: diff canvas backdrop
92
92
  const CAP = 2000
93
93
  // Bumped on every renderDiff entry so a render whose async work (image decode) is overtaken
94
94
  // by a newer mode/image change bails out instead of drawing stale pixels onto the canvas.
@@ -157,7 +157,7 @@ function onRefInput(e: Event) {
157
157
  <template>
158
158
  <div class="rounded-lg border border-default bg-default/60 p-3">
159
159
  <div class="mb-2 flex items-center justify-between gap-2">
160
- <h3 class="min-w-0 truncate text-[12px] font-semibold text-default">{{ view }}</h3>
160
+ <h3 class="min-w-0 truncate text-xs font-semibold text-default">{{ view }}</h3>
161
161
  <!-- Mode switch -->
162
162
  <div
163
163
  v-if="MODES.length > 1"
@@ -166,7 +166,7 @@ function onRefInput(e: Event) {
166
166
  <button
167
167
  v-for="m in MODES"
168
168
  :key="m.id"
169
- class="rounded px-1.5 py-1 text-muted hover:text-default"
169
+ class="rounded-sm px-1.5 py-1 text-muted hover:text-default"
170
170
  :class="mode === m.id ? 'bg-elevated text-app-100' : ''"
171
171
  :title="m.label"
172
172
  @click="mode = m.id"
@@ -179,12 +179,12 @@ function onRefInput(e: Event) {
179
179
  <!-- SIDE BY SIDE -->
180
180
  <div v-if="mode === 'side-by-side'" class="grid grid-cols-2 gap-3">
181
181
  <figure class="space-y-1">
182
- <figcaption class="text-[10px] uppercase tracking-wide text-dimmed">
182
+ <figcaption class="text-3xs uppercase tracking-wide text-dimmed">
183
183
  {{ t('media.compare.actual') }}
184
184
  </figcaption>
185
185
  <button
186
186
  v-if="actualUrl"
187
- class="block w-full overflow-hidden rounded border border-default hover:border-app-600"
187
+ class="block w-full overflow-hidden rounded-sm border border-default hover:border-app-600"
188
188
  @click="actualId && emit('expand', actualId)"
189
189
  >
190
190
  <img
@@ -195,7 +195,7 @@ function onRefInput(e: Event) {
195
195
  </button>
196
196
  <div
197
197
  v-else
198
- class="flex h-32 items-center justify-center rounded border border-dashed border-muted text-[11px] text-app-600"
198
+ class="flex h-32 items-center justify-center rounded-sm border border-dashed border-muted text-2xs text-app-600"
199
199
  >
200
200
  {{
201
201
  props.blobs.statusFor(actualId) === 'error'
@@ -208,7 +208,7 @@ function onRefInput(e: Event) {
208
208
  </figure>
209
209
 
210
210
  <figure class="space-y-1">
211
- <figcaption class="text-[10px] uppercase tracking-wide text-dimmed">
211
+ <figcaption class="text-3xs uppercase tracking-wide text-dimmed">
212
212
  {{ t('media.compare.reference') }}
213
213
  <span v-if="referenceOrigin === 'design'" class="text-app-warning-300/80">
214
214
  {{ t('media.compare.fromLinkedDesign') }}
@@ -216,7 +216,7 @@ function onRefInput(e: Event) {
216
216
  </figcaption>
217
217
  <button
218
218
  v-if="refUrl"
219
- class="group relative block w-full overflow-hidden rounded border border-default hover:border-app-600"
219
+ class="group relative block w-full overflow-hidden rounded-sm border border-default hover:border-app-600"
220
220
  @click="referenceId && emit('expand', referenceId)"
221
221
  >
222
222
  <img
@@ -225,7 +225,7 @@ function onRefInput(e: Event) {
225
225
  class="w-full cursor-zoom-in"
226
226
  />
227
227
  <span
228
- class="absolute bottom-1 end-1 rounded bg-app-950/80 px-1.5 py-0.5 text-[10px] text-toned opacity-0 group-hover:opacity-100"
228
+ class="absolute bottom-1 end-1 rounded-sm bg-app-950/80 px-1.5 py-0.5 text-3xs text-toned opacity-0 group-hover:opacity-100"
229
229
  @click.stop="refInput?.click()"
230
230
  >
231
231
  {{ t('media.compare.replace') }}
@@ -234,7 +234,7 @@ function onRefInput(e: Event) {
234
234
  <!-- Drop zone when no reference yet -->
235
235
  <div
236
236
  v-else
237
- class="flex h-32 cursor-pointer flex-col items-center justify-center gap-1 rounded border border-dashed text-[11px] transition"
237
+ class="flex h-32 cursor-pointer flex-col items-center justify-center gap-1 rounded-sm border border-dashed text-2xs transition"
238
238
  :class="
239
239
  dragOver
240
240
  ? 'border-app-warning-500 bg-app-warning-500/5 text-app-warning-300'
@@ -253,7 +253,7 @@ function onRefInput(e: Event) {
253
253
 
254
254
  <!-- OVERLAY (onion-skin) -->
255
255
  <div v-else-if="mode === 'overlay'" class="space-y-2">
256
- <div class="relative w-full overflow-hidden rounded border border-default">
256
+ <div class="relative w-full overflow-hidden rounded-sm border border-default">
257
257
  <img :src="refUrl" :alt="t('media.compare.referenceAlt', { view })" class="w-full" />
258
258
  <!-- object-contain so a differing aspect ratio onion-skins undistorted over the reference. -->
259
259
  <img
@@ -263,7 +263,7 @@ function onRefInput(e: Event) {
263
263
  :style="{ opacity: overlayOpacity / 100 }"
264
264
  />
265
265
  </div>
266
- <div class="flex items-center gap-2 text-[10px] uppercase tracking-wide text-dimmed">
266
+ <div class="flex items-center gap-2 text-2xs uppercase tracking-wide text-muted">
267
267
  <span>{{ t('media.compare.reference') }}</span>
268
268
  <input
269
269
  v-model.number="overlayOpacity"
@@ -280,7 +280,7 @@ function onRefInput(e: Event) {
280
280
  <div
281
281
  v-else-if="mode === 'swipe'"
282
282
  ref="swipeBox"
283
- class="relative w-full cursor-ew-resize select-none overflow-hidden rounded border border-default"
283
+ class="relative w-full cursor-ew-resize select-none overflow-hidden rounded-sm border border-default"
284
284
  @pointerdown="onSwipeDown"
285
285
  @pointermove="moveSwipe"
286
286
  @pointerup="onSwipeUp"
@@ -312,11 +312,11 @@ function onRefInput(e: Event) {
312
312
  </span>
313
313
  </div>
314
314
  <span
315
- class="absolute left-1 top-1 rounded bg-app-950/70 px-1 text-[9px] uppercase text-toned"
315
+ class="absolute left-1 top-1 rounded-sm bg-app-950/70 px-1 text-3xs uppercase text-toned"
316
316
  >{{ t('media.compare.actual') }}</span
317
317
  >
318
318
  <span
319
- class="absolute right-1 top-1 rounded bg-app-950/70 px-1 text-[9px] uppercase text-toned"
319
+ class="absolute right-1 top-1 rounded-sm bg-app-950/70 px-1 text-3xs uppercase text-toned"
320
320
  >{{ t('media.compare.reference') }}</span
321
321
  >
322
322
  </div>
@@ -324,7 +324,7 @@ function onRefInput(e: Event) {
324
324
  <!-- DIFF (canvas) -->
325
325
  <div v-else-if="mode === 'diff'" class="space-y-1">
326
326
  <canvas ref="diffCanvas" :class="DIFF_CANVAS_CLASS" />
327
- <p class="text-[10px] text-dimmed">{{ t('media.compare.diffHint') }}</p>
327
+ <p class="text-3xs text-dimmed">{{ t('media.compare.diffHint') }}</p>
328
328
  </div>
329
329
 
330
330
  <!-- Hidden file input shared by replace/drop zone -->
@@ -10,6 +10,7 @@
10
10
  import { computed } from 'vue'
11
11
  import { REVIEW_EFFORTS } from '@cat-factory/contracts'
12
12
  import type { ChangeClass, MergeClassRollup, ReviewEffort } from '~/types/merge'
13
+ import SectionLabel from '~/components/common/SectionLabel.vue'
13
14
 
14
15
  const props = defineProps<{
15
16
  /** The currently picked effort, or null for "not chosen". */
@@ -87,12 +88,9 @@ function pick(effort: ReviewEffort) {
87
88
  <template>
88
89
  <div data-testid="merge-effort-chips" class="mt-2">
89
90
  <div class="flex items-center gap-1.5">
90
- <span
91
- class="text-[10px] uppercase tracking-wide text-dimmed"
92
- :title="t('merge.effort.promptHint')"
93
- >
91
+ <SectionLabel as="span" :title="t('merge.effort.promptHint')">
94
92
  {{ t('merge.effort.prompt') }}
95
- </span>
93
+ </SectionLabel>
96
94
  <UBadge
97
95
  v-if="classLabel"
98
96
  data-testid="merge-effort-class"
@@ -119,7 +117,7 @@ function pick(effort: ReviewEffort) {
119
117
  {{ chip.label }}
120
118
  </UButton>
121
119
  </div>
122
- <p v-if="history" data-testid="merge-effort-history" class="mt-1 text-[10px] text-dimmed">
120
+ <p v-if="history" data-testid="merge-effort-history" class="mt-1 text-3xs text-dimmed">
123
121
  {{ history }}
124
122
  </p>
125
123
  </div>