@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
@@ -19,6 +19,8 @@ import {
19
19
  trendMagnitude,
20
20
  } from './ReportsPanel.logic'
21
21
  import ReportsSpendBreakdown from '~/components/panels/ReportsSpendBreakdown.vue'
22
+ import SectionLabel from '~/components/common/SectionLabel.vue'
23
+ import IconButton from '~/components/common/IconButton.vue'
22
24
 
23
25
  // Reports: cross-cutting usage analytics for the active account — where the spend and the
24
26
  // work actually go. Spend per model and agent kind, spend + run activity per workspace /
@@ -136,6 +138,14 @@ const maxRuns = computed(() => maxOf(activityByDimension.value, (row) => row.run
136
138
 
137
139
  /** Boards the filter offers — the active account's, since the report is account-scoped. */
138
140
  const boards = computed(() => workspace.accountWorkspaces)
141
+ // "Every board" is a NAMED sentinel, not an empty string: an empty string is what a Select
142
+ // reserves for "nothing selected", and Reka throws on an item that carries one. The store holds
143
+ // the same state as null, so the two are mapped at the boundary below.
144
+ const ALL_BOARDS = 'all'
145
+ const boardFilterItems = computed(() => [
146
+ { label: t('reports.filter.allBoards'), value: ALL_BOARDS },
147
+ ...boards.value.map((board) => ({ label: board.name, value: board.id })),
148
+ ])
139
149
 
140
150
  function trendTooltip(point: { start: number; meteredCost: number; subscriptionCost: number }) {
141
151
  return `${d(new Date(point.start), 'short')} · ${t('reports.legend.metered')} ${money(point.meteredCost)} · ${t('reports.legend.subscription')} ${money(point.subscriptionCost)}`
@@ -205,55 +215,52 @@ watch(
205
215
  </div>
206
216
  <!-- Filters in ONE row above the charts: window, then board scope. -->
207
217
  <div class="ms-auto flex flex-wrap items-center gap-1.5">
208
- <select
209
- class="rounded-lg border border-default bg-default px-2.5 py-1.5 text-[12px] text-default"
210
- :value="reports.workspaceFilter ?? ''"
218
+ <USelect
219
+ :model-value="reports.workspaceFilter ?? ALL_BOARDS"
220
+ :items="boardFilterItems"
221
+ size="xs"
211
222
  :aria-label="t('reports.filter.board')"
212
223
  data-testid="reports-board-filter"
213
- @change="
214
- reports.setWorkspaceFilter(($event.target as HTMLSelectElement).value || null)
224
+ @update:model-value="
225
+ reports.setWorkspaceFilter($event === ALL_BOARDS ? null : String($event))
215
226
  "
227
+ />
228
+ <UTabs
229
+ :model-value="reports.window"
230
+ :items="WINDOWS"
231
+ :content="false"
232
+ size="xs"
233
+ class="me-1"
234
+ @update:model-value="reports.setWindow($event as ReportWindow)"
216
235
  >
217
- <option value="">{{ t('reports.filter.allBoards') }}</option>
218
- <option v-for="board in boards" :key="board.id" :value="board.id">
219
- {{ board.name }}
220
- </option>
221
- </select>
222
- <div class="me-1 flex rounded-lg border border-default p-0.5 text-[12px]">
223
- <button
224
- v-for="opt in WINDOWS"
225
- :key="opt.value"
226
- class="rounded-md px-2.5 py-1 transition"
227
- :class="
228
- reports.window === opt.value
229
- ? 'bg-elevated text-app-100'
230
- : 'text-muted hover:text-default'
231
- "
232
- :data-testid="`reports-window-${opt.value}`"
233
- @click="reports.setWindow(opt.value)"
234
- >
235
- {{ opt.label }}
236
- </button>
237
- </div>
238
- <button
239
- class="rounded-lg border border-default p-1.5 text-muted transition hover:text-default"
240
- :aria-label="t('reports.refresh')"
241
- :title="t('reports.refresh')"
236
+ <!-- UTabs renders its own triggers and forwards nothing from an item, so this
237
+ slot is the one place a stable per-window selector can live. -->
238
+ <template #default="{ item }">
239
+ <span :data-testid="`reports-window-${item.value}`">{{ item.label }}</span>
240
+ </template>
241
+ </UTabs>
242
+ <IconButton
243
+ color="neutral"
244
+ variant="ghost"
245
+ icon="i-lucide-refresh-cw"
246
+ :label="t('reports.refresh')"
247
+ :ui="{
248
+ base: 'rounded-lg border border-default p-1.5 text-muted transition hover:text-default',
249
+ leadingIcon: loading ? 'h-4 w-4 animate-spin' : 'h-4 w-4',
250
+ }"
242
251
  @click="refresh"
243
- >
244
- <UIcon
245
- name="i-lucide-refresh-cw"
246
- class="h-4 w-4"
247
- :class="{ 'animate-spin': loading }"
248
- />
249
- </button>
250
- <button
251
- class="rounded-lg border border-default p-1.5 text-muted transition hover:text-default"
252
- :aria-label="t('common.close')"
252
+ />
253
+ <IconButton
254
+ color="neutral"
255
+ variant="ghost"
256
+ icon="i-lucide-x"
257
+ :label="t('common.close')"
258
+ :ui="{
259
+ base: 'rounded-lg border border-default p-1.5 text-muted transition hover:text-default',
260
+ leadingIcon: 'h-4 w-4',
261
+ }"
253
262
  @click="close"
254
- >
255
- <UIcon name="i-lucide-x" class="h-4 w-4" />
256
- </button>
263
+ />
257
264
  </div>
258
265
  </header>
259
266
 
@@ -264,12 +271,14 @@ watch(
264
271
  >
265
272
  <p>{{ t('reports.error') }}</p>
266
273
  <p v-if="error" class="mt-1 text-xs text-app-error-300/80">{{ error }}</p>
267
- <button
274
+ <UButton
275
+ color="neutral"
276
+ variant="ghost"
268
277
  class="mt-2 rounded-md border border-app-error-700 px-3 py-1 text-xs hover:bg-app-error-900/40"
269
278
  @click="refresh"
270
279
  >
271
280
  {{ t('reports.retry') }}
272
- </button>
281
+ </UButton>
273
282
  </div>
274
283
 
275
284
  <div v-else-if="loading && !view" class="py-16 text-center text-sm text-muted">
@@ -277,7 +286,7 @@ watch(
277
286
  </div>
278
287
 
279
288
  <div v-else-if="view" class="mx-auto flex max-w-5xl flex-col gap-6">
280
- <p class="text-[11px] text-dimmed">
289
+ <p class="text-2xs text-dimmed">
281
290
  {{
282
291
  t('reports.period', {
283
292
  from: d(new Date(view.since), 'short'),
@@ -308,7 +317,7 @@ watch(
308
317
  </p>
309
318
  <p
310
319
  v-else-if="rollupState === 'current'"
311
- class="text-[11px] text-dimmed"
320
+ class="text-2xs text-dimmed"
312
321
  data-testid="reports-rollup-current"
313
322
  >
314
323
  {{
@@ -320,9 +329,9 @@ watch(
320
329
 
321
330
  <!-- Headline totals. A stat tile, not a chart: these are single numbers. -->
322
331
  <section>
323
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
332
+ <SectionLabel as="h2" class="mb-2">
324
333
  {{ t('reports.totals.title') }}
325
- </h2>
334
+ </SectionLabel>
326
335
  <div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
327
336
  <div class="rounded-lg border border-default bg-default/40 p-3">
328
337
  <p
@@ -352,16 +361,16 @@ watch(
352
361
  <p class="text-xs text-dimmed">{{ t('reports.totals.tokens') }}</p>
353
362
  </div>
354
363
  </div>
355
- <p class="mt-1.5 text-[10px] text-dimmed">
364
+ <p class="mt-1.5 text-3xs text-dimmed">
356
365
  {{ t('reports.totals.illustrative') }}
357
366
  </p>
358
367
  </section>
359
368
 
360
369
  <!-- Spend over time. One axis, two stacked series, legend always present. -->
361
370
  <section>
362
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
371
+ <SectionLabel as="h2" class="mb-2">
363
372
  {{ t('reports.trend.title') }}
364
- </h2>
373
+ </SectionLabel>
365
374
  <div class="rounded-lg border border-default bg-default/40 p-4">
366
375
  <div v-if="!hasSpend" class="py-6 text-center text-xs text-dimmed">
367
376
  {{ t('reports.trend.empty') }}
@@ -383,7 +392,7 @@ watch(
383
392
  />
384
393
  </div>
385
394
  </div>
386
- <div class="mt-2 flex items-center gap-4 text-[11px] text-dimmed">
395
+ <div class="mt-2 flex items-center gap-4 text-2xs text-dimmed">
387
396
  <span class="flex items-center gap-1" :title="t('reports.legend.meteredHint')">
388
397
  <span class="h-2 w-2 rounded-sm bg-app-secondary-500" />{{
389
398
  t('reports.legend.metered')
@@ -404,9 +413,9 @@ watch(
404
413
  <!-- Spend by model + by agent kind: the two axes a run has no single value for. -->
405
414
  <div class="grid gap-6 md:grid-cols-2">
406
415
  <section>
407
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
416
+ <SectionLabel as="h2" class="mb-2">
408
417
  {{ t('reports.spend.byModel') }}
409
- </h2>
418
+ </SectionLabel>
410
419
  <ReportsSpendBreakdown
411
420
  :rows="view.spend.byModel"
412
421
  :currency="currency"
@@ -415,9 +424,9 @@ watch(
415
424
  />
416
425
  </section>
417
426
  <section>
418
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
427
+ <SectionLabel as="h2" class="mb-2">
419
428
  {{ t('reports.spend.byAgentKind') }}
420
- </h2>
429
+ </SectionLabel>
421
430
  <ReportsSpendBreakdown
422
431
  :rows="view.spend.byAgentKind"
423
432
  :currency="currency"
@@ -434,9 +443,9 @@ watch(
434
443
  breakdowns the projection caps, and each says so under its own card. -->
435
444
  <div class="grid gap-6 md:grid-cols-2">
436
445
  <section>
437
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
446
+ <SectionLabel as="h2" class="mb-2">
438
447
  {{ t('reports.spend.byTicket') }}
439
- </h2>
448
+ </SectionLabel>
440
449
  <ReportsSpendBreakdown
441
450
  :rows="view.spend.byTicket"
442
451
  :currency="currency"
@@ -446,9 +455,9 @@ watch(
446
455
  />
447
456
  </section>
448
457
  <section>
449
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
458
+ <SectionLabel as="h2" class="mb-2">
450
459
  {{ t('reports.spend.byRun') }}
451
- </h2>
460
+ </SectionLabel>
452
461
  <ReportsSpendBreakdown
453
462
  :rows="view.spend.byRun"
454
463
  :currency="currency"
@@ -462,29 +471,24 @@ watch(
462
471
  <!-- The shared axis: spend AND activity for the same grouping, side by side. -->
463
472
  <section class="flex flex-col gap-3">
464
473
  <div class="flex flex-wrap items-center gap-2">
465
- <h2 class="text-xs font-semibold uppercase tracking-wide text-dimmed">
474
+ <SectionLabel as="h2">
466
475
  {{ t('reports.breakdown.title') }}
467
- </h2>
468
- <div class="flex rounded-lg border border-default p-0.5 text-[12px]">
469
- <button
470
- v-for="opt in DIMENSIONS"
471
- :key="opt.value"
472
- class="rounded-md px-2.5 py-1 transition"
473
- :class="
474
- dimension === opt.value
475
- ? 'bg-elevated text-app-100'
476
- : 'text-muted hover:text-default'
477
- "
478
- :data-testid="`reports-dimension-${opt.value}`"
479
- @click="dimension = opt.value"
480
- >
481
- {{ opt.label }}
482
- </button>
483
- </div>
476
+ </SectionLabel>
477
+ <UTabs
478
+ :model-value="dimension"
479
+ :items="DIMENSIONS"
480
+ :content="false"
481
+ size="xs"
482
+ @update:model-value="dimension = $event as ReportActivityDimension"
483
+ >
484
+ <template #default="{ item }">
485
+ <span :data-testid="`reports-dimension-${item.value}`">{{ item.label }}</span>
486
+ </template>
487
+ </UTabs>
484
488
  </div>
485
489
  <div class="grid gap-6 md:grid-cols-2">
486
490
  <div>
487
- <h3 class="mb-2 text-[11px] text-dimmed">
491
+ <h3 class="mb-2 text-2xs text-dimmed">
488
492
  {{ t('reports.spend.heading') }}
489
493
  </h3>
490
494
  <ReportsSpendBreakdown
@@ -495,7 +499,7 @@ watch(
495
499
  />
496
500
  </div>
497
501
  <div>
498
- <h3 class="mb-2 text-[11px] text-dimmed">
502
+ <h3 class="mb-2 text-2xs text-dimmed">
499
503
  {{ t('reports.activity.heading') }}
500
504
  </h3>
501
505
  <div class="rounded-lg border border-default bg-default/40 p-4">
@@ -532,7 +536,7 @@ watch(
532
536
  </div>
533
537
  <!-- The status counts in text: the reserved status hues are
534
538
  red-green adjacent, so identity is never colour alone. -->
535
- <p class="mt-1 flex flex-wrap gap-x-2 text-[10px] text-dimmed">
539
+ <p class="mt-1 flex flex-wrap gap-x-2 text-3xs text-dimmed">
536
540
  <span v-for="segment in activitySegments(row)" :key="segment.status">
537
541
  {{ n(segment.count, 'decimal') }} {{ statusLabel(segment.status) }}
538
542
  </span>
@@ -545,7 +549,7 @@ watch(
545
549
  </p>
546
550
  </li>
547
551
  </ul>
548
- <div class="mt-3 flex flex-wrap items-center gap-3 text-[11px] text-dimmed">
552
+ <div class="mt-3 flex flex-wrap items-center gap-3 text-2xs text-dimmed">
549
553
  <span
550
554
  v-for="status in ['done', 'failed', 'running', 'other'] as const"
551
555
  :key="status"
@@ -66,7 +66,7 @@ const max = computed(() => maxOf(props.rows, spendMagnitude))
66
66
  :style="{ width: `${segmentPct(row.subscriptionCost, max)}%` }"
67
67
  />
68
68
  </div>
69
- <p class="mt-1 text-[10px] text-dimmed">
69
+ <p class="mt-1 text-3xs text-dimmed">
70
70
  {{ t('reports.spend.calls', { count: row.calls }, row.calls) }} ·
71
71
  {{
72
72
  t('reports.spend.tokens', {
@@ -77,7 +77,7 @@ const max = computed(() => maxOf(props.rows, spendMagnitude))
77
77
  </p>
78
78
  </li>
79
79
  </ul>
80
- <p v-if="cap" class="mt-3 text-[10px] text-dimmed" :data-testid="`${testId}-capped`">
80
+ <p v-if="cap" class="mt-3 text-3xs text-dimmed" :data-testid="`${testId}-capped`">
81
81
  {{
82
82
  t('reports.spend.capped', { shown: n(cap.returned), omitted: n(cap.omitted) }, cap.omitted)
83
83
  }}