@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,9 +1,12 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, watch } from 'vue'
3
3
  import { onKeyStroke } from '@vueuse/core'
4
+ import type { TableColumn } from '@nuxt/ui'
4
5
  import type { PlatformObservabilityWindow } from '~/types/execution'
5
6
  import { formatMs } from '~/utils/observability'
6
7
  import { FAILURE_KIND_KEYS, isAgentFailureKind } from '~/utils/failureKinds'
8
+ import SectionLabel from '~/components/common/SectionLabel.vue'
9
+ import IconButton from '~/components/common/IconButton.vue'
7
10
 
8
11
  // Deployment-level (platform-operator) observability dashboard: the aggregate health of the
9
12
  // active account's runs — outcome totals + success rate, a time-bucketed outcome trend, the
@@ -67,6 +70,19 @@ function barPct(count: number, max: number): number {
67
70
  // Share of a gate kind's runs the precheck satisfied outright, 0..1: the number the
68
71
  // precheck-before-escalate design exists to move. Null (not 0) when nothing settled, because
69
72
  // "no gates ran" is not "every gate needed a fixer".
73
+ // Column definitions rather than hand-written `<th>`s: the header typography is the table
74
+ // theme's, shared with every other table in the SPA.
75
+ // The count columns are flush right, so their headers are too.
76
+ const NUMERIC = { class: { th: 'text-end' } }
77
+ const gateColumns = computed<TableColumn<NonNullable<typeof view.value>['gates'][number]>[]>(() => [
78
+ { id: 'gate', header: t('platformObservability.gates.gate') },
79
+ { id: 'settled', header: t('platformObservability.gates.settled'), meta: NUMERIC },
80
+ { id: 'cleanPasses', header: t('platformObservability.gates.cleanPasses'), meta: NUMERIC },
81
+ { id: 'attempts', header: t('platformObservability.gates.attempts'), meta: NUMERIC },
82
+ { id: 'helperFailures', header: t('platformObservability.gates.helperFailures'), meta: NUMERIC },
83
+ { id: 'exhausted', header: t('platformObservability.gates.exhausted'), meta: NUMERIC },
84
+ ])
85
+
70
86
  function cleanRate(stat: { gates: number; cleanPasses: number }): number | null {
71
87
  return stat.gates > 0 ? stat.cleanPasses / stat.gates : null
72
88
  }
@@ -124,44 +140,44 @@ watch(
124
140
  <p v-if="accountName" class="truncate text-xs text-dimmed">{{ accountName }}</p>
125
141
  </div>
126
142
  <div class="ms-auto flex items-center gap-1.5">
127
- <div class="me-1 flex rounded-lg border border-default p-0.5 text-[12px]">
128
- <button
129
- v-for="opt in WINDOWS"
130
- :key="opt.value"
131
- class="rounded-md px-2.5 py-1 transition"
132
- :class="
133
- platform.window === opt.value
134
- ? 'bg-elevated text-app-100'
135
- : 'text-muted hover:text-default'
136
- "
137
- :data-testid="`operator-window-${opt.value}`"
138
- @click="setWindow(opt.value)"
139
- >
140
- {{ opt.label }}
141
- </button>
142
- </div>
143
- <button
144
- class="rounded-lg border border-default p-1.5 text-muted transition hover:text-default"
145
- :title="t('platformObservability.refresh')"
146
- :aria-label="t('platformObservability.refresh')"
143
+ <UTabs
144
+ :model-value="platform.window"
145
+ :items="WINDOWS"
146
+ :content="false"
147
+ size="xs"
148
+ class="me-1"
149
+ @update:model-value="setWindow($event as PlatformObservabilityWindow)"
150
+ >
151
+ <!-- UTabs renders its own triggers and forwards nothing from an item, so this
152
+ slot is the one place a stable per-window selector can live. -->
153
+ <template #default="{ item }">
154
+ <span :data-testid="`operator-window-${item.value}`">{{ item.label }}</span>
155
+ </template>
156
+ </UTabs>
157
+ <IconButton
158
+ color="neutral"
159
+ variant="ghost"
160
+ icon="i-lucide-refresh-cw"
161
+ :label="t('platformObservability.refresh')"
162
+ :ui="{
163
+ base: 'rounded-lg border border-default p-1.5 text-muted transition hover:text-default',
164
+ leadingIcon: loading ? 'h-4 w-4 animate-spin' : 'h-4 w-4',
165
+ }"
147
166
  data-testid="operator-refresh"
148
167
  @click="refresh"
149
- >
150
- <UIcon
151
- name="i-lucide-refresh-cw"
152
- class="h-4 w-4"
153
- :class="{ 'animate-spin': loading }"
154
- />
155
- </button>
156
- <button
157
- class="rounded-lg border border-default p-1.5 text-muted transition hover:text-default"
158
- :title="t('platformObservability.close')"
159
- :aria-label="t('platformObservability.close')"
168
+ />
169
+ <IconButton
170
+ color="neutral"
171
+ variant="ghost"
172
+ icon="i-lucide-x"
173
+ :label="t('platformObservability.close')"
174
+ :ui="{
175
+ base: 'rounded-lg border border-default p-1.5 text-muted transition hover:text-default',
176
+ leadingIcon: 'h-4 w-4',
177
+ }"
160
178
  data-testid="operator-close"
161
179
  @click="close"
162
- >
163
- <UIcon name="i-lucide-x" class="h-4 w-4" />
164
- </button>
180
+ />
165
181
  </div>
166
182
  </header>
167
183
 
@@ -171,12 +187,14 @@ watch(
171
187
  class="mx-auto max-w-2xl rounded-lg border border-app-error-800/60 bg-app-error-950/40 p-4 text-sm text-app-error-200"
172
188
  >
173
189
  <p>{{ error }}</p>
174
- <button
190
+ <UButton
191
+ color="neutral"
192
+ variant="ghost"
175
193
  class="mt-2 rounded-md border border-app-error-700 px-3 py-1 text-xs hover:bg-app-error-900/40"
176
194
  @click="refresh"
177
195
  >
178
196
  {{ t('platformObservability.retry') }}
179
- </button>
197
+ </UButton>
180
198
  </div>
181
199
 
182
200
  <div v-else-if="loading && !view" class="py-16 text-center text-sm text-muted">
@@ -221,9 +239,9 @@ watch(
221
239
 
222
240
  <!-- Outcome summary tiles -->
223
241
  <section>
224
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
242
+ <SectionLabel as="h2" class="mb-2">
225
243
  {{ t('platformObservability.outcomes.title') }}
226
- </h2>
244
+ </SectionLabel>
227
245
  <div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
228
246
  <div class="rounded-lg border border-default bg-default/40 p-3">
229
247
  <p class="text-2xl font-semibold text-highlighted">
@@ -269,9 +287,9 @@ watch(
269
287
 
270
288
  <!-- Outcome trend sparkline -->
271
289
  <section>
272
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
290
+ <SectionLabel as="h2" class="mb-2">
273
291
  {{ t('platformObservability.trend.title') }}
274
- </h2>
292
+ </SectionLabel>
275
293
  <div class="rounded-lg border border-default bg-default/40 p-4">
276
294
  <div v-if="view.outcomes.total === 0" class="py-6 text-center text-xs text-dimmed">
277
295
  {{ t('platformObservability.trend.empty') }}
@@ -297,7 +315,7 @@ watch(
297
315
  />
298
316
  </div>
299
317
  </div>
300
- <div class="mt-2 flex items-center gap-4 text-[11px] text-dimmed">
318
+ <div class="mt-2 flex items-center gap-4 text-2xs text-dimmed">
301
319
  <span class="flex items-center gap-1"
302
320
  ><span class="h-2 w-2 rounded-sm bg-app-success-500/80" />{{
303
321
  t('platformObservability.trend.done')
@@ -319,70 +337,61 @@ watch(
319
337
 
320
338
  <!-- Gate / CI-fixer attempt statistics -->
321
339
  <section>
322
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
340
+ <SectionLabel as="h2" class="mb-2">
323
341
  {{ t('platformObservability.gates.title') }}
324
- </h2>
342
+ </SectionLabel>
325
343
  <div class="overflow-x-auto rounded-lg border border-default bg-default/40 p-4">
326
344
  <p v-if="!view.gates.length" class="py-4 text-center text-xs text-dimmed">
327
345
  {{ t('platformObservability.gates.empty') }}
328
346
  </p>
329
- <table v-else class="w-full text-left text-xs" data-testid="operator-gates">
330
- <thead class="text-[11px] uppercase tracking-wide text-dimmed">
331
- <tr>
332
- <th class="pb-2 pe-3 font-medium">
333
- {{ t('platformObservability.gates.gate') }}
334
- </th>
335
- <th class="pb-2 pe-3 text-end font-medium">
336
- {{ t('platformObservability.gates.settled') }}
337
- </th>
338
- <th class="pb-2 pe-3 text-end font-medium">
339
- {{ t('platformObservability.gates.cleanPasses') }}
340
- </th>
341
- <th class="pb-2 pe-3 text-end font-medium">
342
- {{ t('platformObservability.gates.attempts') }}
343
- </th>
344
- <th class="pb-2 pe-3 text-end font-medium">
345
- {{ t('platformObservability.gates.helperFailures') }}
346
- </th>
347
- <th class="pb-2 text-end font-medium">
348
- {{ t('platformObservability.gates.exhausted') }}
349
- </th>
350
- </tr>
351
- </thead>
352
- <tbody class="text-toned">
353
- <tr
354
- v-for="g in view.gates"
355
- :key="g.gateKind"
356
- class="border-t border-default/70"
347
+ <UTable
348
+ v-else
349
+ :data="view.gates"
350
+ :columns="gateColumns"
351
+ :ui="{
352
+ base: 'text-xs',
353
+ th: 'ps-0 pe-3 py-2',
354
+ td: 'ps-0 pe-3 py-2 text-xs text-toned whitespace-normal',
355
+ }"
356
+ data-testid="operator-gates"
357
+ >
358
+ <template #gate-cell="{ row }">
359
+ <span class="font-medium text-default">{{ row.original.gateKind }}</span>
360
+ <span v-if="row.original.helperKind" class="ms-1.5 text-dimmed"
361
+ >&rarr; {{ row.original.helperKind }}</span
357
362
  >
358
- <td class="py-2 pe-3">
359
- <span class="font-medium text-default">{{ g.gateKind }}</span>
360
- <span v-if="g.helperKind" class="ms-1.5 text-dimmed"
361
- >&rarr; {{ g.helperKind }}</span
362
- >
363
- </td>
364
- <td class="py-2 pe-3 text-end tabular-nums">{{ g.gates }}</td>
365
- <td class="py-2 pe-3 text-end tabular-nums">
366
- <span class="text-app-success-400">{{ g.cleanPasses }}</span>
367
- <span v-if="cleanRate(g) !== null" class="ms-1 text-dimmed"
368
- >({{ n(cleanRate(g) ?? 0, 'percent') }})</span
369
- >
370
- </td>
371
- <td class="py-2 pe-3 text-end tabular-nums">{{ g.attempts }}</td>
372
- <td class="py-2 pe-3 text-end tabular-nums">
373
- <span :class="g.helperFailures > 0 ? 'text-app-warning-400' : ''">{{
374
- g.helperFailures
375
- }}</span>
376
- </td>
377
- <td class="py-2 text-end tabular-nums">
378
- <span :class="g.exhausted > 0 ? 'text-app-error-400' : ''">{{
379
- g.exhausted
380
- }}</span>
381
- </td>
382
- </tr>
383
- </tbody>
384
- </table>
385
- <p class="mt-3 text-[11px] leading-relaxed text-dimmed">
363
+ </template>
364
+ <template #settled-cell="{ row }">
365
+ <span class="block text-end tabular-nums">{{ row.original.gates }}</span>
366
+ </template>
367
+ <template #cleanPasses-cell="{ row }">
368
+ <span class="block text-end tabular-nums">
369
+ <span class="text-app-success-400">{{ row.original.cleanPasses }}</span>
370
+ <span v-if="cleanRate(row.original) !== null" class="ms-1 text-dimmed"
371
+ >({{ n(cleanRate(row.original) ?? 0, 'percent') }})</span
372
+ >
373
+ </span>
374
+ </template>
375
+ <template #attempts-cell="{ row }">
376
+ <span class="block text-end tabular-nums">{{ row.original.attempts }}</span>
377
+ </template>
378
+ <template #helperFailures-cell="{ row }">
379
+ <span class="block text-end tabular-nums">
380
+ <span
381
+ :class="row.original.helperFailures > 0 ? 'text-app-warning-400' : ''"
382
+ >{{ row.original.helperFailures }}</span
383
+ >
384
+ </span>
385
+ </template>
386
+ <template #exhausted-cell="{ row }">
387
+ <span class="block text-end tabular-nums">
388
+ <span :class="row.original.exhausted > 0 ? 'text-app-error-400' : ''">{{
389
+ row.original.exhausted
390
+ }}</span>
391
+ </span>
392
+ </template>
393
+ </UTable>
394
+ <p class="mt-3 text-2xs leading-relaxed text-dimmed">
386
395
  {{ t('platformObservability.gates.hint') }}
387
396
  </p>
388
397
  </div>
@@ -391,9 +400,9 @@ watch(
391
400
  <div class="grid gap-6 md:grid-cols-2">
392
401
  <!-- Failure taxonomy -->
393
402
  <section>
394
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
403
+ <SectionLabel as="h2" class="mb-2">
395
404
  {{ t('platformObservability.failures.title') }}
396
- </h2>
405
+ </SectionLabel>
397
406
  <div class="rounded-lg border border-default bg-default/40 p-4">
398
407
  <div v-if="!view.failures.length" class="py-4 text-center text-xs text-dimmed">
399
408
  {{ t('platformObservability.failures.empty') }}
@@ -418,16 +427,16 @@ watch(
418
427
  <!-- Live depth + durations -->
419
428
  <section class="flex flex-col gap-4">
420
429
  <div>
421
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
430
+ <SectionLabel as="h2" class="mb-2">
422
431
  {{ t('platformObservability.live.title') }}
423
- </h2>
432
+ </SectionLabel>
424
433
  <div
425
434
  class="grid grid-cols-4 gap-2 rounded-lg border border-default bg-default/40 p-3 text-center"
426
435
  data-testid="operator-live"
427
436
  >
428
437
  <div>
429
438
  <p class="text-lg font-semibold text-app-info-400">{{ view.live.running }}</p>
430
- <p class="text-[11px] text-dimmed">
439
+ <p class="text-2xs text-dimmed">
431
440
  {{ t('platformObservability.outcomes.running') }}
432
441
  </p>
433
442
  </div>
@@ -435,28 +444,28 @@ watch(
435
444
  <p class="text-lg font-semibold text-app-warning-400">
436
445
  {{ view.live.blocked }}
437
446
  </p>
438
- <p class="text-[11px] text-dimmed">
447
+ <p class="text-2xs text-dimmed">
439
448
  {{ t('platformObservability.outcomes.blocked') }}
440
449
  </p>
441
450
  </div>
442
451
  <div>
443
452
  <p class="text-lg font-semibold text-toned">{{ view.live.paused }}</p>
444
- <p class="text-[11px] text-dimmed">
453
+ <p class="text-2xs text-dimmed">
445
454
  {{ t('platformObservability.outcomes.paused') }}
446
455
  </p>
447
456
  </div>
448
457
  <div>
449
458
  <p class="text-lg font-semibold text-toned">{{ view.live.pending }}</p>
450
- <p class="text-[11px] text-dimmed">
459
+ <p class="text-2xs text-dimmed">
451
460
  {{ t('platformObservability.outcomes.pending') }}
452
461
  </p>
453
462
  </div>
454
463
  </div>
455
464
  </div>
456
465
  <div>
457
- <h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
466
+ <SectionLabel as="h2" class="mb-2">
458
467
  {{ t('platformObservability.durations.title') }}
459
- </h2>
468
+ </SectionLabel>
460
469
  <div class="rounded-lg border border-default bg-default/40 p-3 text-sm">
461
470
  <div
462
471
  v-if="view.durations.count === 0"
@@ -470,7 +479,7 @@ watch(
470
479
  data-testid="operator-durations"
471
480
  >
472
481
  <div>
473
- <dt class="text-[11px] text-dimmed">
482
+ <dt class="text-2xs text-dimmed">
474
483
  {{ t('platformObservability.durations.avg') }}
475
484
  </dt>
476
485
  <dd class="font-semibold text-highlighted">
@@ -478,7 +487,7 @@ watch(
478
487
  </dd>
479
488
  </div>
480
489
  <div>
481
- <dt class="text-[11px] text-dimmed">
490
+ <dt class="text-2xs text-dimmed">
482
491
  {{ t('platformObservability.durations.min') }}
483
492
  </dt>
484
493
  <dd class="font-semibold text-toned">
@@ -486,7 +495,7 @@ watch(
486
495
  </dd>
487
496
  </div>
488
497
  <div>
489
- <dt class="text-[11px] text-dimmed">
498
+ <dt class="text-2xs text-dimmed">
490
499
  {{ t('platformObservability.durations.max') }}
491
500
  </dt>
492
501
  <dd class="font-semibold text-toned">
@@ -494,7 +503,7 @@ watch(
494
503
  </dd>
495
504
  </div>
496
505
  <div>
497
- <dt class="text-[11px] text-dimmed">
506
+ <dt class="text-2xs text-dimmed">
498
507
  {{ t('platformObservability.durations.p50') }}
499
508
  </dt>
500
509
  <dd class="font-semibold text-toned" data-testid="operator-duration-p50">
@@ -502,7 +511,7 @@ watch(
502
511
  </dd>
503
512
  </div>
504
513
  <div>
505
- <dt class="text-[11px] text-dimmed">
514
+ <dt class="text-2xs text-dimmed">
506
515
  {{ t('platformObservability.durations.p90') }}
507
516
  </dt>
508
517
  <dd class="font-semibold text-toned" data-testid="operator-duration-p90">
@@ -510,7 +519,7 @@ watch(
510
519
  </dd>
511
520
  </div>
512
521
  <div>
513
- <dt class="text-[11px] text-dimmed">
522
+ <dt class="text-2xs text-dimmed">
514
523
  {{ t('platformObservability.durations.p99') }}
515
524
  </dt>
516
525
  <dd class="font-semibold text-toned" data-testid="operator-duration-p99">