@cat-factory/app 0.308.0 → 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 (243) 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 +1 -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/utils/theme/builtins.ts +7 -1
  243. package/package.json +1 -1
package/README.md CHANGED
@@ -300,6 +300,27 @@ Every colour in the SPA is a token that follows the THEME and the colour MODE. T
300
300
  - **Never** a raw Tailwind hue (`bg-slate-900`, `text-amber-300`, `text-pink-300`) nor a fixed numbered alias (`text-primary-400`, `bg-warning-950/40`): the first is welded to one palette and one mode, the second follows the palette but not the mode. `scripts/check-frontend-palette.mjs` (CI) bans both. `red` and `rose` both mean `error`.
301
301
  - **Never** a fixed `white` / `black` utility (`bg-white`, `text-black`, `border-white/5`, `bg-white/[0.02]`): invisible or wrong in light mode. Use `border-inverted` (a selection ring), `border-default` / `bg-muted` (faint chrome) or `text-inverted` (text over a filled surface). A line that genuinely needs a fixed value says why with a `fixed-colour-ok:` comment, on that line or the one above it.
302
302
 
303
+ ### Type through named steps, never a pixel literal
304
+
305
+ Every font size in the SPA is a NAMED step, so it scales with the theme and lines up with the sizes Nuxt UI gives its own components. Two sources of steps:
306
+
307
+ - **Tailwind's own** from `text-xs` up (`text-xs`, `text-sm`, `text-base`, ...), each paired with a line height.
308
+ - **The app's two steps below them** (`app/assets/css/type.css`), for the dense surfaces Tailwind's scale bottoms out above: `text-2xs` (0.6875rem, 11px at the default root) for the dense body size that panel rows, node metadata and inline detail are written at, and `text-3xs` (0.625rem, 10px) for the caption tier below it: pill and badge text on a board node, a quoted source excerpt, an axis or legend caption. The section eyebrow is `text-2xs`, not `text-3xs`, and is not written by hand at all (see the last bullet). Neither declares a paired LINE HEIGHT: dense text inherits its rhythm from the surrounding block, and a block that wants a specific rhythm says `leading-*`, which wins over a step's paired value anyway.
309
+ - **Never** a literal size (`text-[11px]`, `text-[0.6875rem]`). A theme document's `fontSize` is applied as a root `font-size` (`utils/theme/css.ts`), so every rem step follows it and a pixel literal does not: raising the font size would grow Nuxt UI's components while the app's own text sat still. A literal is also unnameable, so a `UBadge size="sm"` beside one is sized by a system the label cannot refer to. `scripts/check-frontend-type-scale.mjs` (CI) bans the utility forms (`text-[length:11px]`, `[font-size:11px]`, `[font:…]`), a px declaration in CSS (`font-size: 11px`, the `font:` shorthand) and a px JS style property (`fontSize: '11px'`, `style.fontSize = …`); its header lists exactly what it matches. A line that genuinely needs one says why with a `type-literal-ok:` comment, on that line or the one above.
310
+ - **A section eyebrow is [`common/SectionLabel.vue`](./app/components/common/SectionLabel.vue)**, not a hand-written class list. The label that titles a group inside a panel, window, inspector section or dropdown is one recipe, `text-2xs font-semibold uppercase tracking-wide text-muted`, and the component owns all five: they are not overridable, because a caller wanting a different size or colour wants a different thing. The caller picks the ELEMENT with `as` (`div` by default; `h3` / `h4` for a real heading, `label` with an `:id` for a field; a collapsing section is a `UButton` around `as="span"`) and owns SPACING through the ordinary `class` attribute. An ACCENT-coloured uppercase label (`text-primary`, `text-app-info-400`) is not this component: it is signalling state or category rather than titling a section, so it keeps its own classes. Nor is a pill, a chip, a metadata row that merely shares the uppercase styling (`BlockNode.vue`'s composition line), or a caption inside a control (a switcher trigger's label line, a slider's end labels): the test is whether the text TITLES the block after it. The colour is `text-muted` and not the fainter `text-dimmed` for contrast: at 11px `text-dimmed` is 2.63:1 on a light surface, under WCAG AA, where `text-muted` is 4.76:1. (The SPA's wider use of `text-dimmed` outside this component is a separate problem.)
311
+
312
+ ### Radius through the theme scale
313
+
314
+ Every `rounded-*` utility in the SPA derives from `--ui-radius`, so a theme document's `radius` reaches all of them. Nuxt UI rebuilds seven of Tailwind's steps on it (`rounded-xs` is half it, `rounded-sm` is it, then 1.5x, 2x, 3x, 4x and 6x), and [`main.css`](./app/assets/css/main.css) binds the two spellings that sat outside that: `--radius`, which the bare `rounded` alias and its side and corner forms resolve through, and `--radius-4xl`. Tailwind declares both `@theme default`, so the app's own `@theme inline` block wins, and `inline` is what makes the utility compile to `var(--ui-radius)` rather than to a value frozen at build time. Neither number moves on the default theme.
315
+
316
+ That is the whole mechanism, and it is why there is no rule here about WHICH step to pick. Two things still name a number instead of the scale:
317
+
318
+ - **Never an arbitrary value**: `rounded-[10px]`, `rounded-t-[0.6rem]`, or Tailwind v4's CSS-variable shorthand `rounded-(--x)`. Welded to one number the way a pixel font size is, and unnameable besides, so a `UBadge` beside one is shaped by a system the box cannot refer to. The SPA has none.
319
+ - **In CSS, reach the scale through `var(--ui-radius)`**, on its own or inside a `calc()`: `calc(var(--ui-radius) * 2)` is the `rounded-lg` step, and `calc(var(--ui-radius) - 1px)` is an inner box kept concentric with its parent, which follows the theme just as well. **Never a `--radius-*` theme variable**, even though one works today: Tailwind declares that scale in a `@theme default inline` block and emits a variable only where the COMPILED stylesheet graph references it, so `var(--radius-lg)` is correct until the last other reference is deleted and then silently computes to 0. `--ui-radius` is an ordinary custom property on `:root`, so it is there in a scoped SFC `<style>` block too, where the `--radius-*` scale never is. **Never a raw literal** in px, pt, rem or em, the per-corner longhands (`border-top-left-radius`, the logical `border-start-start-radius`) and the JS `borderRadius` / `'border-radius'` spelling a `:style` binding uses included. The pill spellings (`9999px`, `50%`) and `0` are exempt, and a shorthand is read value by value, so `50% 50% 8px 8px` is still caught on the `8px`.
320
+ - `scripts/check-frontend-radius.mjs` (CI) bans both. It needs no notion of where a class list lives, because `rounded-[` and `rounded-(` are not English words. A line that genuinely needs a fixed radius says why with a `radius-literal-ok:` comment, on that line or the one above.
321
+
322
+ The default `radius` lives on the `Cat Factory` document in `app/utils/theme/builtins.ts`. It equals `THEME_DEFAULTS.radius` and so writes no CSS, which is the point: changing the number there is the whole edit.
323
+
303
324
  ### Themes and colour mode
304
325
 
305
326
  A **theme** is a Nuxt UI theme editor document (https://ui.nuxt.com/theme): the sparse `ThemeDoc` the editor edits and shares as a `?doc=` link, decoded by `utils/theme/link.ts` and typed in `utils/theme/doc.ts` (a port of the editor engine's tables). The app speaks THAT format rather than one of its own, so a theme built in the editor imports as-is and a built-in theme is a document a user could have exported. `utils/theme/builtins.ts` ships `Cat Factory` (the default; its `colors` MUST equal `app.config.ts`'s `ui.colors`, which paints the first frame) and the editor's own `Mono` preset, referenced from the preset table rather than copied. The `theme` store persists the pick and the user's imported documents in this browser; `plugins/theme.client.ts` applies the active document at runtime: aliases onto `appConfig.ui.colors` (Nuxt UI's own colours plugin re-emits the ramps), default variants and component overrides onto `appConfig.ui.<component>` merged INTO the layer's own overrides, and tokens / radius / fonts onto one `<style id="app-theme">` whose selectors anchor on the `data-theme` attribute on `<html>` (`utils/theme/css.ts`, which is also the write boundary for untrusted values). The faces the built-in themes name (Geist, Geist Mono) are self-hosted through `@nuxt/fonts` (`fonts.families` in `nuxt.config.ts`, pinned by `builtins.spec.ts`); a font an IMPORTED theme names is applied as a family name only, so it renders when the device or the deployment provides it. A editor link whose document equals one of the editor's presets collapses to `{ preset: 'mono' }`; `utils/theme/presets.ts` is a copy of the editor's preset table so such a link rebuilds (a decode table, not menu entries).
@@ -6,6 +6,28 @@
6
6
  /* The app's own mode-adaptive colour tokens (`app-*`), layered on Nuxt UI's. */
7
7
  @import './tokens.css';
8
8
 
9
+ /* The app's own type steps below `text-xs` (`text-2xs`, `text-3xs`). */
10
+ @import './type.css';
11
+
12
+ /* Bind the last two radius steps Nuxt UI leaves alone onto `--ui-radius`, so EVERY `rounded-*`
13
+ * utility follows a theme document's `radius` (issue #2251).
14
+ *
15
+ * Nuxt UI rebuilds seven of Tailwind's steps on `--ui-radius` (`--radius-sm` is it, `--radius-lg`
16
+ * is twice it, and so on), which is why a hand-written box on a named step already lines up with
17
+ * the Nuxt UI component beside it. Two spellings sat outside that: the bare `rounded` alias, whose
18
+ * `--radius` Tailwind declares `@theme default inline reference` and so inlines as a literal
19
+ * 0.25rem, and `rounded-4xl`, whose `--radius-4xl` keeps Tailwind's own 2rem. Both are `default`,
20
+ * so declaring them here wins, and `inline` is what lets `rounded` compile to `var(--ui-radius)`
21
+ * rather than to a value frozen at build time.
22
+ *
23
+ * Neither number moves on the default theme: `--ui-radius` is 0.25rem, and 8x it is the 2rem
24
+ * `--radius-4xl` already held. Declaring them here also EMITS both as ordinary custom properties,
25
+ * where Tailwind's `reference` kept `--radius` out of the stylesheet entirely. */
26
+ @theme inline {
27
+ --radius: var(--ui-radius);
28
+ --radius-4xl: calc(var(--ui-radius) * 8);
29
+ }
30
+
9
31
  /* The two toaster rules below are scoped by the `app-toaster` marker class that
10
32
  * `app.config.ts` puts on the toaster's viewport slot, NOT by `[data-slot='viewport']`.
11
33
  * That attribute is Nuxt UI's name for the scroll region of ELEVEN components
@@ -17,7 +17,7 @@
17
17
  .reader-prose.review-mode [data-src-start] {
18
18
  position: relative;
19
19
  cursor: pointer;
20
- border-radius: 0.375rem;
20
+ border-radius: calc(var(--ui-radius) * 1.5);
21
21
  transition: background 0.12s ease;
22
22
  }
23
23
  .reader-prose.review-mode [data-src-start]:hover {
@@ -34,7 +34,7 @@
34
34
  width: 1.1rem;
35
35
  align-items: center;
36
36
  justify-content: center;
37
- border-radius: 0.25rem;
37
+ border-radius: var(--ui-radius);
38
38
  background: var(--ui-primary);
39
39
  color: var(--ui-text-inverted);
40
40
  font-size: 0.8rem;
@@ -81,7 +81,7 @@
81
81
  font-style: italic;
82
82
  }
83
83
  .reader-prose code {
84
- border-radius: 0.25rem;
84
+ border-radius: var(--ui-radius);
85
85
  background: var(--ui-bg-elevated);
86
86
  padding: 0.1rem 0.3rem;
87
87
  font-family: var(--font-mono);
@@ -91,7 +91,7 @@
91
91
  .reader-prose pre {
92
92
  margin: 0.6rem 0;
93
93
  overflow: auto;
94
- border-radius: 0.5rem;
94
+ border-radius: calc(var(--ui-radius) * 2);
95
95
  background: var(--app-bg-canvas);
96
96
  padding: 0.75rem 0.9rem;
97
97
  }
@@ -0,0 +1,24 @@
1
+ /* The app's own type steps below Tailwind's `text-xs` (issue #2248).
2
+ *
3
+ * Both are rem, so both scale with a theme document's `fontSize`, which `utils/theme/css.ts` applies
4
+ * as the root `font-size`:
5
+ *
6
+ * --text-2xs 0.6875rem (11px at the 16px default) the dense body size: panel rows, node
7
+ * metadata, inline detail, and the section
8
+ * eyebrow (`common/SectionLabel.vue`).
9
+ * --text-3xs 0.625rem (10px) the caption tier below the eyebrow: pill and
10
+ * badge text on a board node, a quoted source
11
+ * excerpt, axis and legend captions.
12
+ *
13
+ * Neither step declares a line height. Tailwind pairs `--text-<n>` with an optional
14
+ * `--text-<n>--line-height`, and when one exists the utility sets both properties. Dense text here
15
+ * inherits its rhythm from the surrounding block; a block that wants a specific rhythm says so with
16
+ * `leading-*`.
17
+ *
18
+ * `scripts/check-frontend-type-scale.mjs` bans font-size literals, so these two and Tailwind's own
19
+ * steps are the whole scale. */
20
+
21
+ @theme {
22
+ --text-2xs: 0.6875rem;
23
+ --text-3xs: 0.625rem;
24
+ }
@@ -22,6 +22,7 @@
22
22
  // the retry belongs.
23
23
  import type { AssistantActionId, AssistantOutcome } from '~/types/domain'
24
24
  import { answerFor, assistantSurface, revealTarget, submitGate } from './AssistantModal.logic'
25
+ import SectionLabel from '~/components/common/SectionLabel.vue'
25
26
 
26
27
  const { t } = useI18n()
27
28
  const ui = useUiStore()
@@ -254,9 +255,9 @@ function reveal(blockId: string): void {
254
255
 
255
256
  <!-- What it can do, always visible: the catalog is the affordance. -->
256
257
  <div v-if="examples.length" class="space-y-2">
257
- <p class="text-xs font-medium uppercase tracking-wide text-dimmed">
258
+ <SectionLabel as="p">
258
259
  {{ t('assistant.examplesTitle') }}
259
- </p>
260
+ </SectionLabel>
260
261
  <div class="flex flex-col gap-1">
261
262
  <button
262
263
  v-for="entry in examples"
@@ -50,7 +50,7 @@ const items = computed<DropdownMenuItem[][]>(() => [
50
50
  <div class="truncate text-xs font-medium text-highlighted">
51
51
  {{ auth.user.name || auth.user.login }}
52
52
  </div>
53
- <div class="truncate text-[10px] text-dimmed">@{{ auth.user.login }}</div>
53
+ <div class="truncate text-3xs text-dimmed">@{{ auth.user.login }}</div>
54
54
  </div>
55
55
  <UIcon v-if="!collapsed" name="i-lucide-chevron-up" class="h-4 w-4 shrink-0 text-dimmed" />
56
56
  </button>
@@ -202,7 +202,7 @@ const { requestClose } = useUnsavedGuard({
202
202
  declared its candidates and one whose block was unreadable need different fixes. -->
203
203
  <p
204
204
  v-if="noChoiceKey"
205
- class="mb-3 rounded border border-app-warning-500/30 bg-app-warning-500/5 px-3 py-2 text-xs text-app-warning-200"
205
+ class="mb-3 rounded-sm border border-app-warning-500/30 bg-app-warning-500/5 px-3 py-2 text-xs text-app-warning-200"
206
206
  data-testid="binary-candidates-no-choice"
207
207
  >
208
208
  {{ t(noChoiceKey) }}
@@ -211,7 +211,7 @@ const { requestClose } = useUnsavedGuard({
211
211
  <!-- An automatic keep is NOT a review, and must never render as one. -->
212
212
  <p
213
213
  v-if="view.automatic"
214
- class="mb-3 rounded border border-app-600/40 bg-elevated/40 px-3 py-2 text-xs text-toned"
214
+ class="mb-3 rounded-sm border border-app-600/40 bg-elevated/40 px-3 py-2 text-xs text-toned"
215
215
  data-testid="binary-candidates-automatic"
216
216
  >
217
217
  {{ t('binaryCandidates.automatic') }}
@@ -243,7 +243,7 @@ const { requestClose } = useUnsavedGuard({
243
243
  <div
244
244
  v-for="row in group.rows"
245
245
  :key="row.id"
246
- class="rounded border p-2 transition"
246
+ class="rounded-sm border p-2 transition"
247
247
  :class="[
248
248
  selected.includes(row.id) || row.kept
249
249
  ? 'border-app-info-400/60 bg-app-info-500/5'
@@ -268,7 +268,7 @@ const { requestClose } = useUnsavedGuard({
268
268
  that is no longer selected. -->
269
269
  <label
270
270
  v-if="view.awaiting"
271
- class="mb-1.5 flex cursor-pointer items-center gap-2 text-[11px] text-muted"
271
+ class="mb-1.5 flex cursor-pointer items-center gap-2 text-2xs text-muted"
272
272
  @click.stop
273
273
  >
274
274
  <input
@@ -297,14 +297,14 @@ const { requestClose } = useUnsavedGuard({
297
297
  v-else-if="row.previewUrl"
298
298
  :src="row.previewUrl"
299
299
  :alt="row.label ?? row.id"
300
- class="mb-2 max-h-56 w-full rounded object-contain"
300
+ class="mb-2 max-h-56 w-full rounded-sm object-contain"
301
301
  data-testid="binary-candidate-preview"
302
302
  />
303
303
  <!-- No preview is ORDINARY (a private asset store issues no link), so it is stated
304
304
  rather than left as an empty frame the reader reads as a failed generation. -->
305
305
  <p
306
306
  v-else
307
- class="mb-2 flex h-24 items-center justify-center rounded bg-elevated/60 px-2 text-center text-[10px] text-muted"
307
+ class="mb-2 flex h-24 items-center justify-center rounded-sm bg-elevated/60 px-2 text-center text-3xs text-muted"
308
308
  data-testid="binary-candidate-no-preview"
309
309
  >
310
310
  {{ t('binaryCandidates.noPreview') }}
@@ -316,10 +316,10 @@ const { requestClose } = useUnsavedGuard({
316
316
  : t('binaryCandidates.unattributed')
317
317
  }}
318
318
  </p>
319
- <p v-if="row.note" class="mt-1 text-[11px] text-muted">{{ row.note }}</p>
320
- <p class="mt-1 break-all text-[10px] text-dimmed">{{ row.location }}</p>
321
- <p v-if="row.contentType" class="text-[10px] text-dimmed">{{ row.contentType }}</p>
322
- <p v-if="row.kept" class="mt-1 text-[11px] text-app-success-300">
319
+ <p v-if="row.note" class="mt-1 text-2xs text-muted">{{ row.note }}</p>
320
+ <p class="mt-1 break-all text-3xs text-dimmed">{{ row.location }}</p>
321
+ <p v-if="row.contentType" class="text-3xs text-dimmed">{{ row.contentType }}</p>
322
+ <p v-if="row.kept" class="mt-1 text-2xs text-app-success-300">
323
323
  {{
324
324
  row.storeAs
325
325
  ? t('binaryCandidates.keptAs', { id: row.storeAs })
@@ -352,19 +352,19 @@ const { requestClose } = useUnsavedGuard({
352
352
  />
353
353
  <p
354
354
  v-if="missingAliases.length"
355
- class="mt-1 text-[11px] text-app-warning-300"
355
+ class="mt-1 text-2xs text-app-warning-300"
356
356
  data-testid="binary-candidates-missing-alias"
357
357
  >
358
358
  {{ t('binaryCandidates.missingAlias') }}
359
359
  </p>
360
360
  <p
361
361
  v-else-if="duplicateAliases"
362
- class="mt-1 text-[11px] text-app-warning-300"
362
+ class="mt-1 text-2xs text-app-warning-300"
363
363
  data-testid="binary-candidates-duplicate-alias"
364
364
  >
365
365
  {{ t('binaryCandidates.duplicateAlias') }}
366
366
  </p>
367
- <p v-if="candidates.error" class="mt-1 text-[11px] text-app-error-300">
367
+ <p v-if="candidates.error" class="mt-1 text-2xs text-app-error-300">
368
368
  {{ candidates.error }}
369
369
  </p>
370
370
  <div class="mt-2 flex justify-end">
@@ -395,7 +395,7 @@ const { requestClose } = useUnsavedGuard({
395
395
  >
396
396
  <UIcon name="i-lucide-unlink" class="h-8 w-8 opacity-40" />
397
397
  <p class="text-sm">{{ t('binaryCandidates.noRun.title') }}</p>
398
- <p class="max-w-md text-[11px] text-dimmed">{{ t('binaryCandidates.noRun.hint') }}</p>
398
+ <p class="max-w-md text-2xs text-dimmed">{{ t('binaryCandidates.noRun.hint') }}</p>
399
399
  </div>
400
400
  <div
401
401
  v-else-if="absence === 'loading'"
@@ -411,7 +411,7 @@ const { requestClose } = useUnsavedGuard({
411
411
  >
412
412
  <UIcon name="i-lucide-triangle-alert" class="h-8 w-8 text-app-warning-400/70" />
413
413
  <p class="text-sm">{{ t('binaryCandidates.loadFailed') }}</p>
414
- <p class="max-w-md break-words text-[11px] text-dimmed">{{ candidates.error }}</p>
414
+ <p class="max-w-md break-words text-2xs text-dimmed">{{ candidates.error }}</p>
415
415
  <UButton
416
416
  size="xs"
417
417
  color="neutral"
@@ -17,6 +17,7 @@ import { BINARY_OUTPUT_STATE_KEYS, binaryOutputView } from '~/utils/binaryOutput
17
17
  import { binaryCandidateView } from '~/utils/binaryCandidates'
18
18
  import CopyButton from '~/components/common/CopyButton.vue'
19
19
  import StoredAssetView from '~/components/binaryOutput/StoredAssetView.vue'
20
+ import SectionLabel from '~/components/common/SectionLabel.vue'
20
21
 
21
22
  // Two callers, one renderer — the same split `StepEffortReport` makes: the generic step-detail
22
23
  // panel drops it in as a `card` (its own heading + border, among the other detail sections),
@@ -59,20 +60,17 @@ const state = computed(() => {
59
60
  "
60
61
  data-testid="binary-output-report"
61
62
  >
62
- <div
63
- v-if="variant === 'card'"
64
- class="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted"
65
- >
63
+ <SectionLabel v-if="variant === 'card'" class="flex items-center gap-1.5">
66
64
  <UIcon name="i-lucide-image" class="h-3.5 w-3.5" />
67
65
  <span>{{ t('binaryOutput.heading') }}</span>
68
- </div>
66
+ </SectionLabel>
69
67
 
70
68
  <!-- The CANDIDATE decision, when this step compared before delivering. It sits ABOVE the
71
69
  delivery outcome because it happened first, and because "which of four was this" is the
72
70
  question the artifacts below cannot answer. An AUTOMATIC keep says so: nobody looked. -->
73
71
  <p
74
72
  v-if="candidates && candidates.state.candidates.length > 0"
75
- class="text-[12px] leading-relaxed text-toned"
73
+ class="text-xs leading-relaxed text-toned"
76
74
  data-testid="binary-output-candidate-decision"
77
75
  >
78
76
  {{
@@ -92,7 +90,7 @@ const state = computed(() => {
92
90
  <!-- What happened, in one sentence, before any list. Four of the five states have no list
93
91
  at all, and the fifth still needs its qualifications read alongside it. -->
94
92
  <p
95
- class="flex items-start gap-2 text-[12px] leading-relaxed"
93
+ class="flex items-start gap-2 text-xs leading-relaxed"
96
94
  :class="state.tone"
97
95
  data-testid="binary-output-state"
98
96
  :data-state="view.state"
@@ -106,7 +104,7 @@ const state = computed(() => {
106
104
  so — it is a real state (a trait-carrying kind dispatched under an overriding kind
107
105
  records a declaration against a step that never held one), and rendering a blank
108
106
  would read as a missing value rather than an absent comparison. -->
109
- <dl class="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-[11px]">
107
+ <dl class="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-2xs">
110
108
  <dt class="text-dimmed">{{ t('binaryOutput.target') }}</dt>
111
109
  <dd
112
110
  v-if="view.target"
@@ -158,10 +156,8 @@ const state = computed(() => {
158
156
  :label="row.entity ?? row.description"
159
157
  class="mb-2 pe-8"
160
158
  />
161
- <code class="block break-all pe-8 font-mono text-[11px] text-default">{{
162
- row.location
163
- }}</code>
164
- <div class="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-[10px] text-dimmed">
159
+ <code class="block break-all pe-8 font-mono text-2xs text-default">{{ row.location }}</code>
160
+ <div class="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-3xs text-dimmed">
165
161
  <span class="font-mono">{{ row.service }}</span>
166
162
  <!-- The join the report cannot make on its own, and the question a human opens this
167
163
  for: did it go where the step pointed it? -->
@@ -225,7 +221,7 @@ const state = computed(() => {
225
221
  {{ t('binaryOutput.missizedBadge') }}
226
222
  </UBadge>
227
223
  </div>
228
- <p v-if="row.description" class="mt-1 text-[11px] leading-relaxed text-muted">
224
+ <p v-if="row.description" class="mt-1 text-2xs leading-relaxed text-muted">
229
225
  {{ row.description }}
230
226
  </p>
231
227
  </li>
@@ -234,7 +230,7 @@ const state = computed(() => {
234
230
  <!-- Every qualification the report counted, each naming its own number. Never folded into
235
231
  one "some entries were dropped": the fix for an unknown service id is not the fix for
236
232
  a malformed entry, and neither is the fix for a list that stops short of the tail. -->
237
- <ul class="space-y-1 text-[11px] text-app-warning-400">
233
+ <ul class="space-y-1 text-2xs text-app-warning-400">
238
234
  <!-- The step's OWN target went missing from the catalog, and an id the AGENT invented,
239
235
  are two different failures with two different fixes (re-register it, versus correct
240
236
  the declaration). `binaryOutputView` returns them as DISJOINT fields precisely so
@@ -80,7 +80,7 @@ const filename = computed(() => {
80
80
  v-if="isImage && url"
81
81
  :src="url"
82
82
  :alt="label ?? assetId"
83
- class="w-full rounded object-contain"
83
+ class="w-full rounded-sm object-contain"
84
84
  :class="previewClass"
85
85
  data-testid="stored-asset-preview"
86
86
  />
@@ -89,7 +89,7 @@ const filename = computed(() => {
89
89
  frame. -->
90
90
  <p
91
91
  v-else-if="!isImage && url"
92
- class="flex items-center gap-1.5 rounded bg-elevated/60 px-2 py-1.5 text-[11px] text-toned"
92
+ class="flex items-center gap-1.5 rounded-sm bg-elevated/60 px-2 py-1.5 text-2xs text-toned"
93
93
  data-testid="stored-asset-file"
94
94
  >
95
95
  <UIcon name="i-lucide-file" class="h-3.5 w-3.5 shrink-0" />
@@ -97,7 +97,7 @@ const filename = computed(() => {
97
97
  </p>
98
98
  <p
99
99
  v-else-if="status === 'error'"
100
- class="flex items-center gap-1.5 text-[11px] text-app-warning-300"
100
+ class="flex items-center gap-1.5 text-2xs text-app-warning-300"
101
101
  data-testid="stored-asset-error"
102
102
  >
103
103
  <span>{{ t('binaryOutput.asset.loadFailed') }}</span>
@@ -105,13 +105,13 @@ const filename = computed(() => {
105
105
  {{ t('binaryOutput.asset.retry') }}
106
106
  </UButton>
107
107
  </p>
108
- <p v-else class="text-[11px] text-dimmed" data-testid="stored-asset-loading">
108
+ <p v-else class="text-2xs text-dimmed" data-testid="stored-asset-loading">
109
109
  {{ t('binaryOutput.asset.loading') }}
110
110
  </p>
111
111
 
112
112
  <!-- The two things a person does with a delivered asset. Both hang off the object URL, so
113
113
  they appear only once the bytes are in hand. -->
114
- <div v-if="url" class="flex items-center gap-3 text-[11px]">
114
+ <div v-if="url" class="flex items-center gap-3 text-2xs">
115
115
  <a
116
116
  :href="url"
117
117
  target="_blank"
@@ -46,6 +46,7 @@ import {
46
46
  import { descriptorFieldDefaults } from '@cat-factory/contracts'
47
47
  import { pipelineAllowedForManualStart } from '~/utils/pipeline'
48
48
  import { buildTaskTypePickerRows } from '~/utils/taskTypePicker'
49
+ import SectionLabel from '~/components/common/SectionLabel.vue'
49
50
 
50
51
  const ui = useUiStore()
51
52
  // Interface tier. In BASIC mode this form asks for the task itself (type, title,
@@ -938,13 +939,14 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
938
939
  data-testid="task-type-row"
939
940
  :data-task-type-row="row.id"
940
941
  >
941
- <p
942
+ <SectionLabel
942
943
  v-if="row.caption"
943
- class="mb-1 px-1 text-[11px] font-semibold uppercase tracking-wide text-dimmed"
944
+ as="p"
945
+ class="mb-1 px-1"
944
946
  data-testid="task-type-category"
945
947
  >
946
948
  {{ row.caption }}
947
- </p>
949
+ </SectionLabel>
948
950
  <div class="flex flex-wrap gap-1">
949
951
  <UButton
950
952
  v-for="ty in row.choices"
@@ -981,7 +983,7 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
981
983
  </UFormField>
982
984
 
983
985
  <!-- Recurring tasks are configured as a schedule on the service frame. -->
984
- <div v-if="isRecurring" class="rounded-lg border border-default p-3 text-[11px] text-muted">
986
+ <div v-if="isRecurring" class="rounded-lg border border-default p-3 text-2xs text-muted">
985
987
  <template v-if="recurringFrameId">
986
988
  {{ t('board.addTask.recurringWithFrame') }}
987
989
  </template>
@@ -1023,7 +1025,7 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
1023
1025
  :ui="{ base: 'cursor-default text-toned' }"
1024
1026
  />
1025
1027
  </UFormField>
1026
- <p v-if="resolvingIssueBodies" class="text-[11px] text-dimmed">
1028
+ <p v-if="resolvingIssueBodies" class="text-2xs text-dimmed">
1027
1029
  {{ t('board.addTask.loadingIssue') }}
1028
1030
  </p>
1029
1031
 
@@ -1054,7 +1056,7 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
1054
1056
  <span class="text-sm text-default">{{ t('board.addTask.technical') }}</span>
1055
1057
  </template>
1056
1058
  <template #description>
1057
- <span class="text-[11px] text-dimmed">
1059
+ <span class="text-2xs text-dimmed">
1058
1060
  {{ t('board.addTask.technicalHint') }}
1059
1061
  </span>
1060
1062
  </template>
@@ -1106,7 +1108,7 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
1106
1108
  <label
1107
1109
  v-for="phase in BUG_FISHING_PHASES"
1108
1110
  :key="phase.id"
1109
- class="flex items-start gap-2 rounded-md px-1.5 py-1 text-[12px] hover:bg-elevated/40"
1111
+ class="flex items-start gap-2 rounded-md px-1.5 py-1 text-xs hover:bg-elevated/40"
1110
1112
  >
1111
1113
  <input
1112
1114
  v-model="fishingPhaseIds"
@@ -1117,11 +1119,11 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
1117
1119
  />
1118
1120
  <span class="min-w-0">
1119
1121
  <span class="block text-default">{{ phase.title }}</span>
1120
- <span class="block text-[11px] text-dimmed">{{ phase.goal }}</span>
1122
+ <span class="block text-2xs text-dimmed">{{ phase.goal }}</span>
1121
1123
  </span>
1122
1124
  </label>
1123
1125
  </div>
1124
- <p v-if="fishingPhaseIds.length === 0" class="mt-1.5 text-[11px] text-dimmed">
1126
+ <p v-if="fishingPhaseIds.length === 0" class="mt-1.5 text-2xs text-dimmed">
1125
1127
  {{ t('board.addTask.bugFishingFields.angles.allSelected') }}
1126
1128
  </p>
1127
1129
  </UFormField>
@@ -1385,11 +1387,11 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
1385
1387
  </div>
1386
1388
 
1387
1389
  <div v-if="configDescriptors.length" class="space-y-3">
1388
- <span class="text-[11px] font-semibold uppercase tracking-wide text-muted">
1390
+ <SectionLabel as="span">
1389
1391
  {{ t('board.addTask.agentConfiguration') }}
1390
- </span>
1392
+ </SectionLabel>
1391
1393
  <div v-for="d in configDescriptors" :key="d.id" class="space-y-1">
1392
- <div class="text-[11px] text-muted">{{ d.label }}</div>
1394
+ <div class="text-2xs text-muted">{{ d.label }}</div>
1393
1395
  <div v-if="d.type === 'select'" class="flex flex-wrap gap-1">
1394
1396
  <UButton
1395
1397
  v-for="opt in d.options"
@@ -1411,7 +1413,7 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
1411
1413
  :data-testid="`agent-config-${d.id}`"
1412
1414
  @update:model-value="(v: string | number) => setConfig(d.id, String(v))"
1413
1415
  />
1414
- <p class="text-[11px] leading-snug text-dimmed">{{ d.description }}</p>
1416
+ <p class="text-2xs leading-snug text-dimmed">{{ d.description }}</p>
1415
1417
  </div>
1416
1418
  </div>
1417
1419
 
@@ -1440,7 +1442,7 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
1440
1442
  :issues-hint="t('board.addTask.noIssuesHint')"
1441
1443
  />
1442
1444
 
1443
- <p class="text-[11px] text-dimmed">
1445
+ <p class="text-2xs text-dimmed">
1444
1446
  {{ t('board.addTask.plannedHint') }}
1445
1447
  </p>
1446
1448
  </template>
@@ -138,7 +138,7 @@ async function retry() {
138
138
  data-testid="agent-failure-banner"
139
139
  :data-run-kind="run.kind"
140
140
  >
141
- <div class="flex items-center gap-1.5" :class="compact ? 'text-[11px]' : 'text-xs'">
141
+ <div class="flex items-center gap-1.5" :class="compact ? 'text-2xs' : 'text-xs'">
142
142
  <UIcon
143
143
  name="i-lucide-alert-triangle"
144
144
  class="shrink-0 text-app-error-400"
@@ -150,7 +150,7 @@ async function retry() {
150
150
  <p
151
151
  v-if="failure?.message"
152
152
  class="mt-1 leading-snug text-app-error-300/90"
153
- :class="compact ? 'line-clamp-2 text-[10px]' : 'text-[11px]'"
153
+ :class="compact ? 'line-clamp-2 text-3xs' : 'text-2xs'"
154
154
  :title="failure.message"
155
155
  >
156
156
  {{ failure.message }}
@@ -159,7 +159,7 @@ async function retry() {
159
159
  <p
160
160
  v-if="failure?.hint"
161
161
  class="mt-1 leading-snug text-app-error-400/70"
162
- :class="compact ? 'text-[10px]' : 'text-[11px]'"
162
+ :class="compact ? 'text-3xs' : 'text-2xs'"
163
163
  >
164
164
  {{ failure.hint }}
165
165
  </p>
@@ -168,7 +168,7 @@ async function retry() {
168
168
  name the concrete .env fix rather than only pointing at the (unhelpful-here) tab. -->
169
169
  <p
170
170
  v-if="showEnvironmentLocalHint && !compact"
171
- class="mt-1 text-[11px] leading-snug text-app-error-400/70"
171
+ class="mt-1 text-2xs leading-snug text-app-error-400/70"
172
172
  data-testid="agent-failure-environment-local-hint"
173
173
  >
174
174
  {{
@@ -192,15 +192,15 @@ async function retry() {
192
192
  v-if="!compact && failure"
193
193
  :detail="failure.detail"
194
194
  :message="failure.message"
195
- summary-class="text-[10px] text-app-error-400/60 hover:text-app-error-300"
196
- pre-class="bg-app-error-950/60 text-[10px] text-app-error-200/80"
195
+ summary-class="text-3xs text-app-error-400/60 hover:text-app-error-300"
196
+ pre-class="bg-app-error-950/60 text-3xs text-app-error-200/80"
197
197
  />
198
198
 
199
199
  <div class="mt-2 flex flex-wrap items-center gap-2">
200
200
  <button
201
201
  type="button"
202
202
  class="nodrag flex items-center gap-1 rounded-md bg-app-error-900/40 text-app-error-200 hover:bg-app-error-900/70 disabled:opacity-60"
203
- :class="compact ? 'px-2 py-0.5 text-[10px]' : 'px-2 py-1 text-[11px]'"
203
+ :class="compact ? 'px-2 py-0.5 text-3xs' : 'px-2 py-1 text-2xs'"
204
204
  :disabled="retrying || !access.canExecuteRuns.value"
205
205
  :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
206
206
  data-testid="agent-failure-retry"
@@ -217,7 +217,7 @@ async function retry() {
217
217
  v-if="run.kind === 'bootstrap'"
218
218
  type="button"
219
219
  class="nodrag flex items-center gap-1 rounded-md bg-app-error-900/20 text-app-error-300 hover:bg-app-error-900/50"
220
- :class="compact ? 'px-2 py-0.5 text-[10px]' : 'px-2 py-1 text-[11px]'"
220
+ :class="compact ? 'px-2 py-0.5 text-3xs' : 'px-2 py-1 text-2xs'"
221
221
  data-testid="agent-failure-inspect"
222
222
  @click.stop="inspectRun"
223
223
  >
@@ -234,7 +234,7 @@ async function retry() {
234
234
  v-if="isEnvironmentFailure"
235
235
  type="button"
236
236
  class="nodrag flex items-center gap-1 rounded-md bg-app-error-900/20 text-app-error-300 hover:bg-app-error-900/50"
237
- :class="compact ? 'px-2 py-0.5 text-[10px]' : 'px-2 py-1 text-[11px]'"
237
+ :class="compact ? 'px-2 py-0.5 text-3xs' : 'px-2 py-1 text-2xs'"
238
238
  data-testid="agent-failure-configure-environment"
239
239
  @click.stop="openFailureSetup"
240
240
  >
@@ -19,7 +19,7 @@ const { t } = useI18n()
19
19
  data-testid="agent-failure-history"
20
20
  >
21
21
  <summary
22
- class="flex cursor-pointer items-center gap-1.5 text-[11px] text-muted hover:text-default"
22
+ class="flex cursor-pointer items-center gap-1.5 text-2xs text-muted hover:text-default"
23
23
  >
24
24
  <UIcon name="i-lucide-history" class="h-3.5 w-3.5 shrink-0" />
25
25
  {{ t('board.failure.history.previousErrors', { count: failures.length }, failures.length) }}
@@ -216,7 +216,7 @@ async function create() {
216
216
  <span class="block text-sm font-medium text-default">
217
217
  {{ p.presentation.label }}
218
218
  </span>
219
- <span class="block text-[11px] text-muted">
219
+ <span class="block text-2xs text-muted">
220
220
  {{ p.presentation.description }}
221
221
  </span>
222
222
  </span>
@@ -264,7 +264,7 @@ async function create() {
264
264
  :issues-hint="t('initiative.create.contextIssuesHint')"
265
265
  />
266
266
 
267
- <p class="text-[11px] text-dimmed">
267
+ <p class="text-2xs text-dimmed">
268
268
  {{ t('initiative.create.hint') }}
269
269
  </p>
270
270
  </div>
@@ -27,7 +27,7 @@ const { t } = useI18n()
27
27
  <div class="relative mt-1">
28
28
  <CopyButton :text="detail" class="absolute end-1 top-1 z-10" />
29
29
  <pre
30
- class="max-h-32 overflow-auto whitespace-pre-wrap rounded p-1.5 pe-9"
30
+ class="max-h-32 overflow-auto whitespace-pre-wrap rounded-sm p-1.5 pe-9"
31
31
  :class="preClass"
32
32
  >{{ detail }}</pre>
33
33
  </div>