@cat-factory/app 0.308.0 → 0.310.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/README.md +32 -2
  2. package/app/assets/css/main.css +22 -0
  3. package/app/assets/css/prose.css +4 -4
  4. package/app/assets/css/type.css +24 -0
  5. package/app/components/assistant/AssistantModal.vue +8 -6
  6. package/app/components/auth/BackendMisconfiguredScreen.vue +4 -3
  7. package/app/components/auth/LoginScreen.vue +40 -26
  8. package/app/components/auth/ResetPasswordScreen.vue +9 -4
  9. package/app/components/auth/UserMenu.vue +5 -4
  10. package/app/components/binaryCandidates/BinaryCandidatesWindow.vue +48 -37
  11. package/app/components/binaryOutput/BinaryOutputReport.vue +10 -14
  12. package/app/components/binaryOutput/StoredAssetView.vue +13 -11
  13. package/app/components/board/AddTaskModal.vue +30 -27
  14. package/app/components/board/AgentFailureCard.vue +21 -18
  15. package/app/components/board/AgentFailureHistory.vue +18 -9
  16. package/app/components/board/CreateInitiativeModal.vue +7 -6
  17. package/app/components/board/FailureDetail.vue +22 -12
  18. package/app/components/board/FailureHistoryList.vue +5 -5
  19. package/app/components/board/RecurringPipelineModal.vue +12 -11
  20. package/app/components/board/ReviewFrictionDialog.vue +8 -6
  21. package/app/components/board/StepExecutionHistory.vue +8 -8
  22. package/app/components/board/nodes/AgentChip.vue +1 -1
  23. package/app/components/board/nodes/BlockNode.vue +25 -24
  24. package/app/components/board/nodes/DecisionBadge.vue +4 -3
  25. package/app/components/board/nodes/EpicNode.vue +4 -4
  26. package/app/components/board/nodes/FrameSwimlanes.vue +8 -10
  27. package/app/components/board/nodes/InitiativeCard.vue +4 -4
  28. package/app/components/board/nodes/LaneGroup.vue +3 -5
  29. package/app/components/board/nodes/TaskCard.vue +20 -15
  30. package/app/components/board/nodes/TaskLane.vue +3 -3
  31. package/app/components/board/nodes/TaskPipelineMini.vue +21 -18
  32. package/app/components/bootstrap/AdoptionReviewModal.vue +36 -26
  33. package/app/components/bootstrap/BootstrapModal.vue +14 -13
  34. package/app/components/bootstrap/BootstrapRunSteps.vue +1 -1
  35. package/app/components/brainstorm/BrainstormWindow.vue +13 -10
  36. package/app/components/bugFishing/BugFishingWindow.vue +81 -58
  37. package/app/components/clarity/ClarityReviewWindow.vue +13 -10
  38. package/app/components/common/AsyncViewError.vue +1 -1
  39. package/app/components/common/ClarificationItem.vue +6 -6
  40. package/app/components/common/CopyButton.vue +12 -11
  41. package/app/components/common/DescriptorFields.vue +1 -1
  42. package/app/components/common/EmptyState.vue +1 -1
  43. package/app/components/common/IconButton.vue +34 -9
  44. package/app/components/common/InterviewGateNotice.vue +4 -4
  45. package/app/components/common/MarkdownProse.vue +8 -2
  46. package/app/components/common/SectionLabel.vue +38 -0
  47. package/app/components/consensus/ConsensusSessionWindow.vue +10 -9
  48. package/app/components/context/ContextAttachmentFields.vue +32 -20
  49. package/app/components/docs/DocInterviewWindow.vue +10 -9
  50. package/app/components/documents/ContextDocumentPicker.vue +22 -21
  51. package/app/components/documents/DocumentImportModal.vue +7 -5
  52. package/app/components/documents/DocumentOriginLink.vue +12 -9
  53. package/app/components/documents/DocumentSourceConnectModal.vue +2 -2
  54. package/app/components/documents/DocumentSyncState.vue +4 -4
  55. package/app/components/documents/DocumentTemplatesModal.vue +5 -4
  56. package/app/components/documents/RepoContextDocPicker.vue +7 -9
  57. package/app/components/documents/StartFromDesignModal.vue +4 -4
  58. package/app/components/documents/TaskContextDocs.vue +1 -1
  59. package/app/components/environments/EnvSetupStepper.vue +3 -3
  60. package/app/components/environments/EnvironmentStatusPanel.vue +15 -13
  61. package/app/components/environments/steps/EnvPreflightStep.vue +8 -8
  62. package/app/components/environments/steps/EnvReviewStep.vue +23 -22
  63. package/app/components/environments/steps/EnvSaveStep.vue +5 -5
  64. package/app/components/focus/BlockFocusView.vue +11 -10
  65. package/app/components/followUp/FollowUpWindow.vue +18 -15
  66. package/app/components/forkDecision/ForkDecisionWindow.vue +85 -97
  67. package/app/components/foundational/FoundationalContractSummary.vue +2 -2
  68. package/app/components/foundational/FoundationalServiceCatalogList.vue +6 -5
  69. package/app/components/foundational/FoundationalServiceRegistry.vue +10 -9
  70. package/app/components/foundational/FoundationalServiceSources.vue +7 -6
  71. package/app/components/foundational/FoundationalSuppressions.vue +2 -2
  72. package/app/components/fragments/FragmentLibraryManager.vue +8 -7
  73. package/app/components/fragments/FragmentSelector.vue +22 -20
  74. package/app/components/gates/GateFailingCheckList.vue +7 -6
  75. package/app/components/gates/GateResultView.vue +55 -49
  76. package/app/components/github/AddServiceFromRepoModal.vue +16 -11
  77. package/app/components/github/BranchProtectionPreflight.vue +5 -5
  78. package/app/components/github/GitHubConnect.vue +5 -3
  79. package/app/components/github/GitHubOnboarding.vue +7 -2
  80. package/app/components/github/GitHubPanel.vue +23 -19
  81. package/app/components/github/RepoTreeBrowser.vue +16 -13
  82. package/app/components/humanTest/HumanTestWindow.vue +29 -24
  83. package/app/components/initiative/InitiativePlanNotice.vue +3 -3
  84. package/app/components/initiative/InitiativePlanReview.vue +46 -38
  85. package/app/components/initiative/InitiativePlanningWindow.vue +5 -5
  86. package/app/components/initiative/InitiativeTrackerWindow.vue +213 -165
  87. package/app/components/judge/JudgeResultView.vue +28 -27
  88. package/app/components/kaizen/KaizenPanel.vue +70 -56
  89. package/app/components/kaizen/KaizenStepStatus.vue +7 -6
  90. package/app/components/layout/AccountAuditLog.vue +1 -1
  91. package/app/components/layout/AccountDeploymentSettings.vue +10 -10
  92. package/app/components/layout/AccountFailureKindRules.vue +13 -13
  93. package/app/components/layout/AccountFoundationalSettings.vue +1 -1
  94. package/app/components/layout/AccountFragmentSettings.vue +1 -1
  95. package/app/components/layout/AccountModelPolicySettings.vue +7 -7
  96. package/app/components/layout/AccountPlatformAlertSettings.vue +8 -8
  97. package/app/components/layout/AccountRiskPolicySettings.vue +2 -2
  98. package/app/components/layout/AccountRunCredentialSettings.vue +3 -3
  99. package/app/components/layout/AccountSkillSettings.vue +1 -1
  100. package/app/components/layout/AccountTeamSettings.vue +13 -9
  101. package/app/components/layout/AiProvidersBanner.vue +1 -1
  102. package/app/components/layout/AppearanceSwitcher.vue +5 -4
  103. package/app/components/layout/BoardSwitcher.vue +13 -10
  104. package/app/components/layout/BoardToolbar.vue +10 -5
  105. package/app/components/layout/CommandBar.vue +8 -5
  106. package/app/components/layout/DefaultTestEnvBanner.vue +6 -5
  107. package/app/components/layout/IntegrationsHub.vue +12 -9
  108. package/app/components/layout/LanguageSwitcher.vue +5 -4
  109. package/app/components/layout/ModelProvidersHub.vue +10 -7
  110. package/app/components/layout/NotificationsInbox.vue +47 -36
  111. package/app/components/layout/PersonalSetupModal.vue +9 -6
  112. package/app/components/layout/RolePrompt.vue +5 -4
  113. package/app/components/layout/SideBar.vue +7 -8
  114. package/app/components/layout/SpendWarningBanner.vue +3 -3
  115. package/app/components/layout/TranslationWarningBanner.vue +5 -4
  116. package/app/components/layout/UiModeSwitcher.vue +15 -12
  117. package/app/components/layout/UiRoleSwitcher.vue +10 -8
  118. package/app/components/layout/WorkspaceMembersSettings.vue +2 -2
  119. package/app/components/media/ArtifactLightbox.vue +74 -44
  120. package/app/components/media/ImageCompare.vue +80 -85
  121. package/app/components/merge/MergeEffortChips.vue +4 -6
  122. package/app/components/notifications/NotificationSettingsPanel.vue +6 -5
  123. package/app/components/observability/OutcomeFilterChips.vue +6 -5
  124. package/app/components/observability/RunFailureSummary.vue +22 -23
  125. package/app/components/observability/StepMetricsBar.vue +9 -8
  126. package/app/components/observability/StepModelActivity.vue +8 -5
  127. package/app/components/observability/ToolCallList.vue +19 -20
  128. package/app/components/outcome/OutcomeSummaryWindow.vue +48 -46
  129. package/app/components/palettes/AgentPalette.vue +21 -15
  130. package/app/components/palettes/AgentTierSelect.vue +1 -1
  131. package/app/components/palettes/PipelinePurposeSelect.vue +1 -1
  132. package/app/components/panels/AgentStepDetail.vue +57 -49
  133. package/app/components/panels/AttemptEntryHeader.vue +2 -2
  134. package/app/components/panels/DecisionModal.vue +1 -1
  135. package/app/components/panels/GenericStructuredResultView.vue +6 -5
  136. package/app/components/panels/InspectorPanel.vue +15 -13
  137. package/app/components/panels/MergerResultView.vue +10 -9
  138. package/app/components/panels/ObservabilityPanel.vue +211 -186
  139. package/app/components/panels/OperatorDashboardPanel.vue +124 -115
  140. package/app/components/panels/ReportsPanel.vue +87 -83
  141. package/app/components/panels/ReportsSpendBreakdown.vue +2 -2
  142. package/app/components/panels/ResultWindowShell.vue +152 -137
  143. package/app/components/panels/RunDetailLoadState.vue +1 -1
  144. package/app/components/panels/StepContainerStatus.vue +17 -16
  145. package/app/components/panels/StepDelegatedStatus.vue +24 -21
  146. package/app/components/panels/StepEffortReport.vue +12 -17
  147. package/app/components/panels/StepFragmentAdherence.vue +9 -10
  148. package/app/components/panels/StepMetadataCard.vue +41 -43
  149. package/app/components/panels/StepReproductionReport.vue +18 -20
  150. package/app/components/panels/StepRestartControl.vue +3 -2
  151. package/app/components/panels/StepRunMeta.vue +27 -26
  152. package/app/components/panels/StepTestReport.vue +16 -15
  153. package/app/components/panels/StepToolServers.vue +7 -8
  154. package/app/components/panels/StepValidationReport.vue +5 -5
  155. package/app/components/panels/inspector/ContainerSummary.vue +4 -4
  156. package/app/components/panels/inspector/DocReferenceRepos.vue +5 -4
  157. package/app/components/panels/inspector/EnvProbeReport.vue +16 -13
  158. package/app/components/panels/inspector/EpicChildren.vue +12 -10
  159. package/app/components/panels/inspector/FrontendBindingsResolved.vue +5 -4
  160. package/app/components/panels/inspector/FrontendConfig.vue +29 -36
  161. package/app/components/panels/inspector/InitiativeInspector.vue +3 -3
  162. package/app/components/panels/inspector/InspectorSection.vue +25 -17
  163. package/app/components/panels/inspector/RecurringScheduleSettings.vue +9 -8
  164. package/app/components/panels/inspector/ServiceConnections.vue +5 -4
  165. package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +2 -2
  166. package/app/components/panels/inspector/ServiceSelfTests.vue +9 -13
  167. package/app/components/panels/inspector/ServiceTestConfig.vue +46 -49
  168. package/app/components/panels/inspector/ServiceTestSecrets.vue +3 -3
  169. package/app/components/panels/inspector/ServiceTestingContext.vue +2 -2
  170. package/app/components/panels/inspector/ServiceValidationConfig.vue +2 -2
  171. package/app/components/panels/inspector/TaskAgentConfig.vue +2 -2
  172. package/app/components/panels/inspector/TaskAprioriBranches.vue +6 -5
  173. package/app/components/panels/inspector/TaskDependencies.vue +1 -1
  174. package/app/components/panels/inspector/TaskEstimateBadge.vue +3 -7
  175. package/app/components/panels/inspector/TaskExecution.vue +18 -16
  176. package/app/components/panels/inspector/TaskRunSettings.vue +45 -44
  177. package/app/components/panels/inspector/TaskStructure.vue +5 -4
  178. package/app/components/pipeline/AgentPromptEditor.vue +15 -14
  179. package/app/components/pipeline/BinaryOutputStepPicker.vue +42 -52
  180. package/app/components/pipeline/EstimateThresholdFields.vue +14 -11
  181. package/app/components/pipeline/GateConfigFields.vue +19 -23
  182. package/app/components/pipeline/IterationCapPrompt.vue +1 -1
  183. package/app/components/pipeline/PipelineBuilder.vue +113 -99
  184. package/app/components/pipeline/PipelineHealthModal.vue +7 -7
  185. package/app/components/pipeline/PipelinePicker.vue +11 -9
  186. package/app/components/pipeline/PipelinePreview.vue +6 -7
  187. package/app/components/pipeline/PipelineProgress.vue +49 -45
  188. package/app/components/prReview/PrReviewWindow.vue +87 -65
  189. package/app/components/providers/AiPresetMismatchDialog.vue +4 -3
  190. package/app/components/providers/AiProviderOnboardingModal.vue +2 -2
  191. package/app/components/providers/ApiKeysSection.vue +11 -9
  192. package/app/components/providers/PersonalSubscriptionSection.vue +4 -3
  193. package/app/components/providers/VendorCredentialsModal.vue +5 -4
  194. package/app/components/provisioning/ProvisioningLogsDrawer.vue +10 -9
  195. package/app/components/ralph/RalphLoopResultView.vue +29 -29
  196. package/app/components/requirements/RequirementsReviewWindow.vue +22 -17
  197. package/app/components/riskPolicy/RiskPolicyPicker.vue +13 -11
  198. package/app/components/riskPolicy/RiskPolicyPreview.vue +16 -15
  199. package/app/components/sandbox/SandboxPanel.vue +93 -73
  200. package/app/components/settings/ApiTokensPanel.vue +1 -1
  201. package/app/components/settings/BudgetSettings.vue +18 -17
  202. package/app/components/settings/CapabilityCredentialsPanel.vue +5 -5
  203. package/app/components/settings/CloudflareHandlerSection.vue +44 -41
  204. package/app/components/settings/ComposeEnvironmentSetupSection.vue +14 -12
  205. package/app/components/settings/ConnectionTestVerdict.vue +1 -1
  206. package/app/components/settings/ConsensusGroupsSection.vue +58 -47
  207. package/app/components/settings/CustomManifestTypeEditor.vue +8 -7
  208. package/app/components/settings/DefaultProvisionTypeSection.vue +5 -5
  209. package/app/components/settings/InfraHandlersConfigurator.vue +26 -24
  210. package/app/components/settings/InfrastructureBackendPicker.vue +18 -18
  211. package/app/components/settings/IssueTrackerPanel.vue +30 -21
  212. package/app/components/settings/KubernetesEngineForm.vue +9 -8
  213. package/app/components/settings/KubernetesEnvironmentForm.vue +4 -3
  214. package/app/components/settings/LocalContainerPoolSettings.vue +2 -2
  215. package/app/components/settings/LocalModelEndpointsPanel.vue +13 -11
  216. package/app/components/settings/MergeClassRulesEditor.vue +6 -5
  217. package/app/components/settings/MergeRolePolicyEditor.vue +13 -12
  218. package/app/components/settings/ModelConfigurationPanel.vue +17 -19
  219. package/app/components/settings/ModelPresetHealthModal.vue +3 -3
  220. package/app/components/settings/ObservabilityConnectionPanel.vue +1 -1
  221. package/app/components/settings/OpenRouterCatalogPanel.vue +10 -8
  222. package/app/components/settings/PackageRegistriesPanel.vue +4 -4
  223. package/app/components/settings/ProviderConnectionTab.vue +5 -4
  224. package/app/components/settings/ProviderManifestEditor.vue +9 -8
  225. package/app/components/settings/ProviderPreferenceEditor.vue +13 -13
  226. package/app/components/settings/RiskPolicyCreateForm.vue +40 -26
  227. package/app/components/settings/RiskPolicyEditorRow.vue +21 -19
  228. package/app/components/settings/RiskPolicyHealthModal.vue +3 -3
  229. package/app/components/settings/RiskPolicyPanel.vue +11 -13
  230. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +15 -12
  231. package/app/components/settings/SharedStacksPanel.vue +5 -5
  232. package/app/components/settings/TaskSourceCard.vue +2 -2
  233. package/app/components/settings/TaskTypeSuppressionsPanel.vue +4 -4
  234. package/app/components/settings/ToolServerChecklist.vue +22 -22
  235. package/app/components/settings/UsageSettings.vue +13 -13
  236. package/app/components/settings/UserSecretsSection.vue +4 -3
  237. package/app/components/settings/WorkspaceMetadataSettings.vue +6 -5
  238. package/app/components/settings/WorkspaceSettingsPanel.vue +72 -58
  239. package/app/components/skills/ReviewSkillQueue.vue +12 -10
  240. package/app/components/skills/SkillLibraryManager.vue +9 -8
  241. package/app/components/slack/SlackPanel.vue +17 -12
  242. package/app/components/spec/ServiceSpecWindow.vue +19 -19
  243. package/app/components/tasks/BugHuntModal.vue +1 -1
  244. package/app/components/tasks/ContextIssuePicker.vue +16 -15
  245. package/app/components/tasks/TaskContextIssues.vue +5 -4
  246. package/app/components/tasks/TaskImportModal.vue +3 -2
  247. package/app/components/tasks/TaskSourceConnectModal.vue +5 -5
  248. package/app/components/testing/TestReportWindow.vue +67 -60
  249. package/app/components/theme/ThemeImportModal.vue +2 -2
  250. package/app/components/tutorial/TutorialNudge.vue +3 -2
  251. package/app/components/tutorial/TutorialOverlay.vue +3 -2
  252. package/app/components/vcs/GitLabConnect.vue +1 -1
  253. package/app/components/visualConfirm/VisualConfirmationWindow.vue +79 -54
  254. package/app/utils/theme/builtins.ts +7 -1
  255. package/i18n/locales/de.json +7 -3
  256. package/i18n/locales/en.json +7 -3
  257. package/i18n/locales/es.json +7 -3
  258. package/i18n/locales/fr.json +7 -3
  259. package/i18n/locales/he.json +7 -3
  260. package/i18n/locales/it.json +7 -3
  261. package/i18n/locales/ja.json +7 -3
  262. package/i18n/locales/pl.json +7 -3
  263. package/i18n/locales/tr.json +7 -3
  264. package/i18n/locales/uk.json +7 -3
  265. package/package.json +1 -1
@@ -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"
@@ -428,31 +429,34 @@ function revealDecision(n: Notification) {
428
429
  />
429
430
  <div class="min-w-0 flex-1">
430
431
  <div class="flex items-center gap-1.5">
431
- <button
432
- class="block min-w-0 flex-1 truncate text-start text-sm font-medium text-default hover:underline"
432
+ <UButton
433
+ color="neutral"
434
+ variant="link"
435
+ class="block min-w-0 flex-1 truncate p-0 text-start text-sm font-medium text-default hover:underline"
433
436
  :title="n.title"
434
437
  @click="reveal(n)"
435
438
  >
436
439
  {{ n.title }}
437
- </button>
440
+ </UButton>
438
441
  <span
439
442
  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"
443
+ 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
444
  >
442
445
  {{ t('layout.notifications.overdue') }}
443
446
  </span>
444
447
  </div>
445
- <p class="mt-0.5 text-[11px] leading-snug text-muted">{{ n.body }}</p>
446
- <a
448
+ <p class="mt-0.5 text-2xs leading-snug text-muted">{{ n.body }}</p>
449
+ <ULink
450
+ raw
447
451
  v-if="n.payload?.prUrl"
448
- :href="n.payload.prUrl"
452
+ :to="n.payload.prUrl"
449
453
  target="_blank"
450
454
  rel="noopener"
451
- class="mt-1 inline-flex items-center gap-1 text-[11px] text-app-info-400 hover:underline"
455
+ class="mt-1 inline-flex items-center gap-1 text-2xs text-app-info-400 hover:underline"
452
456
  >
453
457
  <UIcon name="i-lucide-external-link" class="h-3 w-3" />
454
458
  {{ t('layout.notifications.openPr') }}
455
- </a>
459
+ </ULink>
456
460
  <!--
457
461
  A platform-health card deep-links to the runs it aggregated, so the operator
458
462
  lands on the evidence rather than only on the dashboard.
@@ -462,31 +466,38 @@ function revealDecision(n: Notification) {
462
466
  class="mt-1.5 flex flex-col gap-0.5"
463
467
  data-testid="notification-failing-runs"
464
468
  >
465
- <component
466
- :is="canOpenFailingRun(run) ? 'button' : 'span'"
467
- v-for="run in failingRuns(n)"
468
- :key="run.executionId"
469
- :type="canOpenFailingRun(run) ? 'button' : undefined"
470
- class="flex items-center gap-1 text-start text-[11px]"
471
- :class="
472
- canOpenFailingRun(run)
473
- ? 'text-app-info-400 hover:underline'
474
- : 'cursor-default text-dimmed'
475
- "
476
- :title="
477
- canOpenFailingRun(run) ? undefined : t('layout.notifications.failingRunGone')
478
- "
479
- @click="canOpenFailingRun(run) && revealFailingRun(run)"
480
- >
481
- <UIcon name="i-lucide-circle-alert" class="h-3 w-3 shrink-0" />
482
- <span class="truncate">{{
483
- t('layout.notifications.failingRun', {
484
- kind: run.failureKind,
485
- at: d(new Date(run.createdAt), 'short'),
486
- })
487
- }}</span>
488
- </component>
489
- <span v-if="failingRunsOmitted(n) > 0" class="text-[11px] text-dimmed">
469
+ <template v-for="run in failingRuns(n)" :key="run.executionId">
470
+ <UButton
471
+ v-if="canOpenFailingRun(run)"
472
+ color="neutral"
473
+ variant="link"
474
+ size="xs"
475
+ class="gap-1 p-0 text-start text-2xs text-app-info-400 hover:text-app-info-400 hover:underline"
476
+ @click="revealFailingRun(run)"
477
+ >
478
+ <UIcon name="i-lucide-circle-alert" class="h-3 w-3 shrink-0" />
479
+ <span class="truncate">{{
480
+ t('layout.notifications.failingRun', {
481
+ kind: run.failureKind,
482
+ at: d(new Date(run.createdAt), 'short'),
483
+ })
484
+ }}</span>
485
+ </UButton>
486
+ <span
487
+ v-else
488
+ class="flex cursor-default items-center gap-1 text-start text-2xs text-dimmed"
489
+ :title="t('layout.notifications.failingRunGone')"
490
+ >
491
+ <UIcon name="i-lucide-circle-alert" class="h-3 w-3 shrink-0" />
492
+ <span class="truncate">{{
493
+ t('layout.notifications.failingRun', {
494
+ kind: run.failureKind,
495
+ at: d(new Date(run.createdAt), 'short'),
496
+ })
497
+ }}</span>
498
+ </span>
499
+ </template>
500
+ <span v-if="failingRunsOmitted(n) > 0" class="text-2xs text-dimmed">
490
501
  {{ t('layout.notifications.failingRunsMore', { count: failingRunsOmitted(n) }) }}
491
502
  </span>
492
503
  </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,14 +119,15 @@ 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
- <button
126
+ <UButton
127
+ color="neutral"
128
+ variant="ghost"
125
129
  v-for="item in group.items"
126
130
  :key="item.key"
127
- type="button"
128
131
  class="flex w-full items-center gap-3 rounded-lg border border-default bg-default/50 px-3 py-2.5 text-start transition hover:border-muted hover:bg-default"
129
132
  @click="item.onClick()"
130
133
  >
@@ -135,7 +138,7 @@ const groups = computed<PersonalGroup[]>(() => {
135
138
  <UBadge v-if="item.connected" color="success" variant="subtle" size="sm">
136
139
  {{ item.status || t('layout.personalSetup.connected') }}
137
140
  </UBadge>
138
- <span v-else class="text-[11px] text-dimmed">
141
+ <span v-else class="text-2xs text-dimmed">
139
142
  {{ t('layout.personalSetup.notConnected') }}
140
143
  </span>
141
144
  </div>
@@ -145,7 +148,7 @@ const groups = computed<PersonalGroup[]>(() => {
145
148
  name="i-lucide-chevron-right"
146
149
  class="h-4 w-4 shrink-0 text-dimmed rtl:-scale-x-100"
147
150
  />
148
- </button>
151
+ </UButton>
149
152
  </div>
150
153
  </section>
151
154
  </div>
@@ -31,10 +31,11 @@ function pick(role: UiRole) {
31
31
  <div class="space-y-4" data-testid="role-prompt">
32
32
  <p class="text-sm text-toned">{{ t('uiRole.prompt.intro') }}</p>
33
33
  <div class="space-y-2">
34
- <button
34
+ <UButton
35
+ color="neutral"
36
+ variant="ghost"
35
37
  v-for="role in UI_ROLES"
36
38
  :key="role"
37
- type="button"
38
39
  :data-testid="`role-option-${role}`"
39
40
  :aria-pressed="role === uiRole.role && uiRole.chosen"
40
41
  class="flex w-full items-center gap-3 rounded-lg border p-3 text-start transition"
@@ -52,11 +53,11 @@ function pick(role: UiRole) {
52
53
  </div>
53
54
  <p class="text-xs text-muted">{{ t(ROLE_PRESENTATION[role].hintKey) }}</p>
54
55
  </div>
55
- </button>
56
+ </UButton>
56
57
  </div>
57
58
  <!-- The choice is not a commitment, and saying so is what makes the narrowed role
58
59
  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>
60
+ <p class="text-2xs leading-snug text-dimmed">{{ t('uiRole.prompt.change') }}</p>
60
61
  </div>
61
62
  </template>
62
63
  <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
 
@@ -225,8 +226,9 @@ watch(
225
226
  <div class="contents" @click="onNavAction">
226
227
  <!-- Command bar launcher (⌘K) — the primary way to create blocks / pipelines
227
228
  and reach every action below. -->
228
- <button
229
- type="button"
229
+ <UButton
230
+ color="neutral"
231
+ variant="ghost"
230
232
  class="flex items-center gap-2 rounded-lg border border-muted bg-elevated/60 py-2 text-start text-sm text-muted transition hover:border-app-500 hover:bg-elevated"
231
233
  :class="railed ? 'justify-center px-0' : 'px-2.5'"
232
234
  :aria-label="t('nav.commandBar')"
@@ -237,7 +239,7 @@ watch(
237
239
  <UIcon name="i-lucide-search" class="h-4 w-4 shrink-0" />
238
240
  <span v-if="!railed" class="flex-1 truncate">{{ t('nav.commandBar') }}</span>
239
241
  <UKbd v-if="!railed" value="⌘K" />
240
- </button>
242
+ </UButton>
241
243
 
242
244
  <!-- Sections + items come from the shared nav manifest, already gated by the
243
245
  reactive slotFilter (backend/docs/adr/0049-modular-vue-adoption.md, slice 1) — which
@@ -248,12 +250,9 @@ watch(
248
250
  <template v-for="section in sidebarGroups" :key="section.group">
249
251
  <USeparator />
250
252
  <section>
251
- <h2
252
- v-if="!railed"
253
- class="mb-2 px-1 text-[11px] font-semibold uppercase tracking-wide text-muted"
254
- >
253
+ <SectionLabel v-if="!railed" as="h2" class="mb-2 px-1">
255
254
  {{ t(section.labelKey) }}
256
- </h2>
255
+ </SectionLabel>
257
256
  <div class="space-y-1.5">
258
257
  <UButton
259
258
  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">
@@ -39,15 +39,16 @@ function dismiss() {
39
39
  <span class="mx-1.5 text-app-warning-400/60">·</span>
40
40
  <i18n-t keypath="language.warning.body" tag="span" scope="global">
41
41
  <template #repoLink>
42
- <a
43
- :href="REPO_URL"
42
+ <ULink
43
+ raw
44
+ :to="REPO_URL"
44
45
  target="_blank"
45
46
  rel="noopener noreferrer"
46
47
  class="inline-flex items-center gap-1 font-medium text-app-info-300 hover:underline"
47
48
  >
48
49
  {{ t('language.warning.repoLinkLabel') }}
49
50
  <UIcon name="i-lucide-external-link" class="h-3 w-3" />
50
- </a>
51
+ </ULink>
51
52
  </template>
52
53
  </i18n-t>
53
54
  </p>
@@ -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" />
@@ -66,9 +67,10 @@ const pinnedTitle = computed(
66
67
  <!-- Rail: one button, flips the tier. The label rides under the glyph so the rail still says
67
68
  which tier is on — the audience that needs to find the advanced half is exactly the one
68
69
  sitting in basic mode's collapsed default. -->
69
- <button
70
+ <UButton
71
+ color="neutral"
72
+ variant="ghost"
70
73
  v-else-if="collapsed"
71
- type="button"
72
74
  data-testid="ui-mode-toggle"
73
75
  :aria-label="t('uiMode.switchTo', { mode: t(MODE_LABELS[otherMode]) })"
74
76
  :title="t('uiMode.switchTo', { mode: t(MODE_LABELS[otherMode]) })"
@@ -76,24 +78,25 @@ const pinnedTitle = computed(
76
78
  @click="uiMode.toggleMode()"
77
79
  >
78
80
  <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">
81
+ <span class="w-full truncate text-center text-3xs font-medium uppercase text-toned">
80
82
  {{ currentLabel }}
81
83
  </span>
82
- </button>
84
+ </UButton>
83
85
 
84
86
  <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">
87
+ <SectionLabel class="mb-1 px-1">
86
88
  {{ t('uiMode.switcher') }}
87
- </div>
89
+ </SectionLabel>
88
90
  <div
89
91
  role="group"
90
92
  :aria-label="t('uiMode.switcher')"
91
93
  class="flex w-full gap-1 rounded-lg border border-muted bg-default/60 p-1"
92
94
  >
93
- <button
95
+ <UButton
96
+ color="neutral"
97
+ variant="ghost"
94
98
  v-for="mode in UI_MODES"
95
99
  :key="mode"
96
- type="button"
97
100
  :data-testid="`ui-mode-option-${mode}`"
98
101
  :aria-pressed="mode === uiMode.mode"
99
102
  class="flex-1 truncate rounded-md px-2 py-1 text-xs font-medium transition"
@@ -105,13 +108,13 @@ const pinnedTitle = computed(
105
108
  @click="uiMode.setMode(mode)"
106
109
  >
107
110
  {{ t(MODE_LABELS[mode]) }}
108
- </button>
111
+ </UButton>
109
112
  </div>
110
113
  </div>
111
114
 
112
115
  <!-- The one-line "what this tier gives you", so the choice is self-explanatory. Dropped in
113
116
  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">
117
+ <p v-if="!collapsed" class="px-1 text-3xs leading-snug text-dimmed">
115
118
  {{ t(MODE_HINTS[uiMode.mode]) }}
116
119
  </p>
117
120
  </template>
@@ -38,23 +38,25 @@ const items = computed(() =>
38
38
  <template>
39
39
  <UDropdownMenu :items="items" :ui="{ content: 'min-w-48' }">
40
40
  <!-- Rail: glyph over the role name, matching the tier button beside it. -->
41
- <button
41
+ <UButton
42
+ color="neutral"
43
+ variant="ghost"
42
44
  v-if="collapsed"
43
- type="button"
44
45
  data-testid="ui-role-toggle"
45
46
  :aria-label="`${t('uiRole.switcher')}: ${currentLabel}`"
46
47
  :title="`${t('uiRole.switcher')}: ${currentLabel}`"
47
48
  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
49
  >
49
50
  <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">
51
+ <span class="w-full truncate text-center text-3xs font-medium uppercase text-toned">
51
52
  {{ currentLabel }}
52
53
  </span>
53
- </button>
54
+ </UButton>
54
55
 
55
- <button
56
+ <UButton
57
+ color="neutral"
58
+ variant="ghost"
56
59
  v-else
57
- type="button"
58
60
  data-testid="ui-role-switcher"
59
61
  :aria-label="t('uiRole.switcher')"
60
62
  :title="t(current.hintKey)"
@@ -62,12 +64,12 @@ const items = computed(() =>
62
64
  >
63
65
  <UIcon :name="current.icon" class="h-4 w-4 shrink-0 text-primary" />
64
66
  <div class="min-w-0 flex-1">
65
- <div class="truncate text-[10px] uppercase tracking-wide text-dimmed">
67
+ <div class="truncate text-2xs uppercase tracking-wide text-muted">
66
68
  {{ t('uiRole.switcher') }}
67
69
  </div>
68
70
  <div class="truncate text-xs font-medium text-default">{{ currentLabel }}</div>
69
71
  </div>
70
72
  <UIcon name="i-lucide-chevron-down" class="h-3.5 w-3.5 shrink-0 text-dimmed" />
71
- </button>
73
+ </UButton>
72
74
  </UDropdownMenu>
73
75
  </template>
@@ -221,7 +221,7 @@ function memberLabel(userId: string, name?: string | null, email?: string | null
221
221
  {{ t('layout.workspaceMembers.add.noAccount') }}
222
222
  </p>
223
223
  <template v-else>
224
- <form class="flex gap-2" @submit.prevent="addMember">
224
+ <UForm class="flex gap-2" @submit="addMember">
225
225
  <USelect
226
226
  v-model="addUserId"
227
227
  :items="candidates"
@@ -242,7 +242,7 @@ function memberLabel(userId: string, name?: string | null, email?: string | null
242
242
  >
243
243
  {{ t('layout.workspaceMembers.add.submit') }}
244
244
  </UButton>
245
- </form>
245
+ </UForm>
246
246
  <p v-if="candidates.length === 0" class="mt-2 text-dimmed">
247
247
  {{ t('layout.workspaceMembers.add.allAdded') }}
248
248
  </p>
@@ -9,6 +9,7 @@
9
9
  import { computed, ref, watch } from 'vue'
10
10
  import { useModalBehavior } from '@modular-vue/core'
11
11
  import type { ArtifactBlobs } from '~/composables/useArtifactBlobs'
12
+ import IconButton from '~/components/common/IconButton.vue'
12
13
 
13
14
  interface LightboxItem {
14
15
  artifactId: string
@@ -173,46 +174,63 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
173
174
  >
174
175
  <!-- Toolbar -->
175
176
  <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">
177
+ <span class="min-w-0 flex-1 truncate text-sm font-medium text-default">
177
178
  {{ current?.label ?? t('media.lightbox.fallbackTitle') }}
178
179
  </span>
179
- <span v-if="total > 1" class="shrink-0 text-[12px] tabular-nums text-muted">
180
+ <span v-if="total > 1" class="shrink-0 text-xs tabular-nums text-muted">
180
181
  {{ t('media.lightbox.counter', { current: index + 1, total }) }}
181
182
  </span>
182
183
  <div class="flex shrink-0 items-center gap-1">
183
- <button
184
- class="rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default disabled:opacity-40"
185
- :title="t('media.lightbox.zoomOut')"
184
+ <IconButton
185
+ color="neutral"
186
+ variant="ghost"
187
+ icon="i-lucide-zoom-out"
188
+ :label="t('media.lightbox.zoomOut')"
189
+ :ui="{
190
+ base: 'rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default disabled:opacity-40',
191
+ leadingIcon: 'h-4 w-4',
192
+ }"
186
193
  :disabled="scale <= MIN_SCALE"
187
194
  @click="zoomBy(1 / 1.25)"
188
- >
189
- <UIcon name="i-lucide-zoom-out" class="h-4 w-4" />
190
- </button>
191
- <span class="w-10 text-center text-[11px] tabular-nums text-dimmed">{{
195
+ />
196
+ <span class="w-10 text-center text-2xs tabular-nums text-dimmed">{{
192
197
  n(scale, 'percent')
193
198
  }}</span>
194
- <button
195
- class="rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default disabled:opacity-40"
196
- :title="t('media.lightbox.zoomIn')"
199
+ <IconButton
200
+ color="neutral"
201
+ variant="ghost"
202
+ icon="i-lucide-zoom-in"
203
+ :label="t('media.lightbox.zoomIn')"
204
+ :ui="{
205
+ base: 'rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default disabled:opacity-40',
206
+ leadingIcon: 'h-4 w-4',
207
+ }"
197
208
  :disabled="scale >= MAX_SCALE"
198
209
  @click="zoomBy(1.25)"
199
- >
200
- <UIcon name="i-lucide-zoom-in" class="h-4 w-4" />
201
- </button>
202
- <button
203
- class="rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default"
204
- :title="t('media.lightbox.reset')"
210
+ />
211
+ <IconButton
212
+ color="neutral"
213
+ variant="ghost"
214
+ icon="i-lucide-maximize"
215
+ :label="t('media.lightbox.reset')"
216
+ :ui="{
217
+ base: 'rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default',
218
+ leadingIcon: 'h-4 w-4',
219
+ }"
205
220
  @click="resetView"
206
- >
207
- <UIcon name="i-lucide-maximize" class="h-4 w-4" />
208
- </button>
209
- <button
210
- class="ms-1 rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default"
211
- :title="t('media.lightbox.close')"
221
+ />
222
+ <IconButton
223
+ color="neutral"
224
+ variant="ghost"
225
+ icon="i-lucide-x"
226
+ class="ms-1"
227
+ :label="t('media.lightbox.close')"
228
+ :ui="{
229
+ base: 'rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default',
230
+ leadingIcon: 'h-4 w-4',
231
+ }"
212
232
  @click="close"
213
- >
214
- <UIcon name="i-lucide-x" class="h-4 w-4" />
215
- </button>
233
+ />
216
234
  </div>
217
235
  </div>
218
236
 
@@ -222,21 +240,26 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
222
240
  @wheel="onWheel"
223
241
  @click.self="close"
224
242
  >
225
- <button
243
+ <IconButton
226
244
  v-if="total > 1"
227
- class="absolute start-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-default/80 p-2 text-toned hover:bg-elevated hover:text-highlighted"
228
- :title="t('media.lightbox.prev')"
245
+ color="neutral"
246
+ variant="ghost"
247
+ icon="i-lucide-chevron-left"
248
+ class="absolute start-3 top-1/2 z-10 -translate-y-1/2"
249
+ :label="t('media.lightbox.prev')"
250
+ :ui="{
251
+ base: 'rounded-full bg-default/80 p-2 text-toned hover:bg-elevated hover:text-highlighted',
252
+ leadingIcon: 'h-5 w-5 rtl:-scale-x-100',
253
+ }"
229
254
  @click="go(-1)"
230
- >
231
- <UIcon name="i-lucide-chevron-left" class="h-5 w-5 rtl:-scale-x-100" />
232
- </button>
255
+ />
233
256
 
234
257
  <img
235
258
  v-if="url"
236
259
  :src="url"
237
260
  :alt="current?.alt ?? ''"
238
261
  draggable="false"
239
- class="max-h-full max-w-full select-none rounded shadow-2xl"
262
+ class="max-h-full max-w-full select-none rounded-sm shadow-2xl"
240
263
  :class="[
241
264
  scale > 1 ? (dragging ? 'cursor-grabbing' : 'cursor-grab') : 'cursor-zoom-in',
242
265
  dragging ? '' : 'transition-transform duration-100',
@@ -254,26 +277,33 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
254
277
  class="h-8 w-8"
255
278
  :class="state === 'error' ? '' : 'animate-spin'"
256
279
  />
257
- <p class="text-[12px]">
280
+ <p class="text-xs">
258
281
  {{ state === 'error' ? t('media.lightbox.failed') : t('media.lightbox.loading') }}
259
282
  </p>
260
- <button
283
+ <UButton
284
+ color="neutral"
285
+ variant="link"
261
286
  v-if="state === 'error' && current"
262
- class="text-[12px] text-app-warning-300 hover:underline"
287
+ class="p-0 text-xs text-app-warning-300 hover:underline"
263
288
  @click="props.blobs.retry(current.artifactId)"
264
289
  >
265
290
  {{ t('common.retry') }}
266
- </button>
291
+ </UButton>
267
292
  </div>
268
293
 
269
- <button
294
+ <IconButton
270
295
  v-if="total > 1"
271
- class="absolute end-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-default/80 p-2 text-toned hover:bg-elevated hover:text-highlighted"
272
- :title="t('media.lightbox.next')"
296
+ color="neutral"
297
+ variant="ghost"
298
+ icon="i-lucide-chevron-right"
299
+ class="absolute end-3 top-1/2 z-10 -translate-y-1/2"
300
+ :label="t('media.lightbox.next')"
301
+ :ui="{
302
+ base: 'rounded-full bg-default/80 p-2 text-toned hover:bg-elevated hover:text-highlighted',
303
+ leadingIcon: 'h-5 w-5 rtl:-scale-x-100',
304
+ }"
273
305
  @click="go(1)"
274
- >
275
- <UIcon name="i-lucide-chevron-right" class="h-5 w-5 rtl:-scale-x-100" />
276
- </button>
306
+ />
277
307
  </div>
278
308
  </div>
279
309
  </Teleport>