@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 { computed, nextTick, reactive, ref, watch } from 'vue'
3
3
  import { onKeyStroke } from '@vueuse/core'
4
+ import type { TableColumn } from '@nuxt/ui'
4
5
  import { isLlmWarningFinishReason } from '@cat-factory/contracts'
5
6
  import type {
6
7
  AgentContextSnapshot,
@@ -29,6 +30,8 @@ import {
29
30
  import OutcomeFilterChips from '~/components/observability/OutcomeFilterChips.vue'
30
31
  import RunFailureSummary from '~/components/observability/RunFailureSummary.vue'
31
32
  import ToolCallList from '~/components/observability/ToolCallList.vue'
33
+ import SectionLabel from '~/components/common/SectionLabel.vue'
34
+ import IconButton from '~/components/common/IconButton.vue'
32
35
 
33
36
  /** No run selected: the same empty, NOT-truncated trajectory the store answers with. */
34
37
  const EMPTY_TRAJECTORY: RunToolCallTrajectory = Object.freeze({
@@ -165,6 +168,22 @@ function openToolsView() {
165
168
  ensureTrajectoryLoaded()
166
169
  }
167
170
 
171
+ const viewTabs = computed(() => [
172
+ { value: 'calls', label: t('observability.modelActivity') },
173
+ { value: 'tools', label: t('observability.toolCalls.title') },
174
+ { value: 'context', label: t('observability.providedContext') },
175
+ { value: 'search', label: t('observability.webSearch') },
176
+ ])
177
+
178
+ /** The tools view has a side effect (lazy trajectory load), so selection routes through here. */
179
+ function selectView(next: string) {
180
+ if (next === 'tools') {
181
+ openToolsView()
182
+ return
183
+ }
184
+ view.value = next as typeof view.value
185
+ }
186
+
168
187
  /**
169
188
  * Load the trajectory the first time it is actually looked at.
170
189
  *
@@ -410,6 +429,23 @@ const costCurrency = computed(
410
429
  const showCost = computed(
411
430
  () => !!costCurrency.value && phaseRows.value.some((p) => p.costEstimate != null),
412
431
  )
432
+ // Column definitions rather than hand-written `<th>`s. The cost column stays conditional on the
433
+ // same signal: a deployment with no rate table must not be shown a column of em dashes.
434
+ const phaseColumns = computed<TableColumn<(typeof phaseRows.value)[number]>[]>(() => [
435
+ { id: 'phase' },
436
+ { id: 'turns' },
437
+ { id: 'tokens' },
438
+ ...(showCost.value ? [{ id: 'cost' } as TableColumn<(typeof phaseRows.value)[number]>] : []),
439
+ // `aria-sort` needs BOTH halves: UTable asks `enableSorting && column.getCanSort()`, and
440
+ // `getCanSort()` is false on a column with no accessor, so `enableSorting` alone emitted
441
+ // nothing and the header silently stopped stating the order. The rows arrive in this order
442
+ // from the rollup, so `manualSorting` below keeps TanStack from re-deriving it: the
443
+ // declaration MARKS the order, it does not produce it, and nothing here offers a sort control.
444
+ { id: 'carryCost', accessorKey: 'carryCostTokens', enableSorting: true },
445
+ ])
446
+ /** The order the rollup already returns, stated so the header can say which column it is. */
447
+ const PHASE_SORTING = [{ id: 'carryCost', desc: true }]
448
+ const PHASE_SORTING_OPTIONS = { manualSorting: true }
413
449
  /**
414
450
  * The run's estimated cost, folded from the same SQL rollup the phase table shows — NOT from
415
451
  * the capped call list the token totals beside it use, which would silently under-report a run
@@ -520,44 +556,17 @@ function exportJson() {
520
556
  </p>
521
557
  </div>
522
558
  <div class="ms-auto flex items-center gap-1.5">
523
- <div class="me-1 flex rounded-lg border border-default p-0.5 text-[12px]">
524
- <button
525
- class="rounded-md px-2.5 py-1 transition"
526
- :class="
527
- view === 'calls' ? 'bg-elevated text-app-100' : 'text-muted hover:text-default'
528
- "
529
- @click="view = 'calls'"
530
- >
531
- {{ t('observability.modelActivity') }}
532
- </button>
533
- <button
534
- class="rounded-md px-2.5 py-1 transition"
535
- :class="
536
- view === 'tools' ? 'bg-elevated text-app-100' : 'text-muted hover:text-default'
537
- "
538
- @click="openToolsView()"
539
- >
540
- {{ t('observability.toolCalls.title') }}
541
- </button>
542
- <button
543
- class="rounded-md px-2.5 py-1 transition"
544
- :class="
545
- view === 'context' ? 'bg-elevated text-app-100' : 'text-muted hover:text-default'
546
- "
547
- @click="view = 'context'"
548
- >
549
- {{ t('observability.providedContext') }}
550
- </button>
551
- <button
552
- class="rounded-md px-2.5 py-1 transition"
553
- :class="
554
- view === 'search' ? 'bg-elevated text-app-100' : 'text-muted hover:text-default'
555
- "
556
- @click="view = 'search'"
557
- >
558
- {{ t('observability.webSearch') }}
559
- </button>
560
- </div>
559
+ <!-- Four VIEWS of the same run, which is what tabs mean. The trajectory is loaded
560
+ lazily the first time the tools view is opened, so the change is watched rather
561
+ than bound straight to `view`. -->
562
+ <UTabs
563
+ :model-value="view"
564
+ :items="viewTabs"
565
+ :content="false"
566
+ size="xs"
567
+ class="me-1"
568
+ @update:model-value="selectView(String($event))"
569
+ />
561
570
  <UButton
562
571
  v-if="view === 'calls'"
563
572
  icon="i-lucide-download"
@@ -571,12 +580,12 @@ function exportJson() {
571
580
  >
572
581
  {{ t('observability.exportJson') }}
573
582
  </UButton>
574
- <UButton
583
+ <IconButton
575
584
  icon="i-lucide-x"
576
585
  color="neutral"
577
586
  variant="ghost"
578
587
  size="sm"
579
- :title="t('observability.closeEsc')"
588
+ :label="t('observability.closeEsc')"
580
589
  @click="close"
581
590
  />
582
591
  </div>
@@ -597,28 +606,28 @@ function exportJson() {
597
606
 
598
607
  <!-- run-level summary -->
599
608
  <section class="rounded-xl border border-default bg-default/50 p-4">
600
- <dl class="grid grid-cols-2 gap-x-6 gap-y-3 text-[13px] sm:grid-cols-4">
609
+ <dl class="grid grid-cols-2 gap-x-6 gap-y-3 text-sm sm:grid-cols-4">
601
610
  <div>
602
- <dt class="text-[11px] uppercase tracking-wide text-dimmed">
611
+ <SectionLabel as="dt">
603
612
  {{ t('observability.summary.calls') }}
604
- </dt>
613
+ </SectionLabel>
605
614
  <dd class="mt-0.5 tabular-nums text-default">{{ totals.calls }}</dd>
606
615
  </div>
607
616
  <div v-if="showCost || !phaseRollup.available">
608
- <dt class="text-[11px] uppercase tracking-wide text-dimmed">
617
+ <SectionLabel as="dt">
609
618
  {{ t('observability.summary.cost') }}
610
- </dt>
619
+ </SectionLabel>
611
620
  <dd class="mt-0.5 tabular-nums text-default">
612
621
  {{ runCost ?? '—' }}
613
- <span class="mt-0.5 block text-[11px] text-dimmed">
622
+ <span class="mt-0.5 block text-2xs text-dimmed">
614
623
  {{ t(costNoteKey) }}
615
624
  </span>
616
625
  </dd>
617
626
  </div>
618
627
  <div>
619
- <dt class="text-[11px] uppercase tracking-wide text-dimmed">
628
+ <SectionLabel as="dt">
620
629
  {{ t('observability.summary.tokensInOut') }}
621
- </dt>
630
+ </SectionLabel>
622
631
  <dd class="mt-0.5 tabular-nums text-default">
623
632
  <span :title="t('observability.summary.inputTokensHint')">
624
633
  {{ formatTokens(totals.inputTokens) }} /
@@ -626,7 +635,7 @@ function exportJson() {
626
635
  </span>
627
636
  <span
628
637
  v-if="totals.cacheReadTokens > 0 || totals.cacheWriteTokens > 0"
629
- class="mt-0.5 block text-[11px]"
638
+ class="mt-0.5 block text-2xs"
630
639
  >
631
640
  <span class="text-muted" :title="t('observability.summary.freshHint')">
632
641
  {{
@@ -665,9 +674,9 @@ function exportJson() {
665
674
  </dd>
666
675
  </div>
667
676
  <div>
668
- <dt class="text-[11px] uppercase tracking-wide text-dimmed">
677
+ <SectionLabel as="dt">
669
678
  {{ t('observability.summary.transportOverhead') }}
670
- </dt>
679
+ </SectionLabel>
671
680
  <dd class="mt-0.5 tabular-nums text-default">
672
681
  <span v-if="totals.transportPct !== null">
673
682
  {{ totals.transportPct }}% · {{ formatMs(totals.overheadMs) }}
@@ -676,9 +685,9 @@ function exportJson() {
676
685
  </dd>
677
686
  </div>
678
687
  <div>
679
- <dt class="text-[11px] uppercase tracking-wide text-dimmed">
688
+ <SectionLabel as="dt">
680
689
  {{ t('observability.summary.modelExecution') }}
681
- </dt>
690
+ </SectionLabel>
682
691
  <dd class="mt-0.5 tabular-nums text-default">
683
692
  {{ formatMs(totals.upstreamMs) }}
684
693
  </dd>
@@ -718,89 +727,112 @@ function exportJson() {
718
727
  class="rounded-xl border border-default bg-default/50 p-4"
719
728
  >
720
729
  <div class="flex items-baseline gap-2">
721
- <h2 class="text-[11px] uppercase tracking-wide text-dimmed">
730
+ <SectionLabel as="h2">
722
731
  {{ t('observability.phase.title') }}
723
- </h2>
724
- <span v-if="phaseRollup.available" class="text-[11px] text-app-600">
732
+ </SectionLabel>
733
+ <span v-if="phaseRollup.available" class="text-2xs text-app-600">
725
734
  {{ t('observability.phase.subtitle') }}
726
735
  </span>
727
736
  </div>
728
737
  <!-- No rollup to fold: said in words, because an absent table and a run that spent
729
738
  nothing look identical, and the calls listed above prove it spent something. -->
730
- <p v-if="!phaseRollup.available" class="mt-2 text-[12px] text-muted">
739
+ <p v-if="!phaseRollup.available" class="mt-2 text-xs text-muted">
731
740
  {{ t('observability.phase.noRollup') }}
732
741
  </p>
733
742
  <div v-else class="mt-3 overflow-x-auto">
734
- <table class="w-full min-w-[32rem] text-[12px]">
735
- <thead>
736
- <tr class="text-[11px] uppercase tracking-wide text-dimmed">
737
- <th class="py-1 pe-3 text-start font-normal">
738
- {{ t('observability.phase.columns.phase') }}
739
- </th>
740
- <th class="py-1 px-3 text-end font-normal">
741
- {{ t('observability.phase.columns.turns') }}
742
- </th>
743
- <th class="py-1 px-3 text-end font-normal">
744
- {{ t('observability.phase.columns.tokensInOut') }}
745
- </th>
746
- <th v-if="showCost" class="py-1 px-3 text-end font-normal">
747
- <span :title="t('observability.phase.costHint')">
748
- {{ t('observability.phase.columns.cost') }}
749
- </span>
750
- </th>
751
- <!-- The sort key, MARKED as one. Rows lead with carry cost rather than
752
- with tokens, and the two orders genuinely differ: a phase that runs
753
- late carries almost nothing however much it spent (nothing after it
754
- re-sends its context). Leaving that implicit invites reading row 1
755
- as "the phase that burned the most", which is the neighbouring
756
- column. -->
757
- <th aria-sort="descending" class="py-1 ps-3 text-end font-normal">
758
- <span :title="t('observability.phase.carryCostHint')">
759
- {{ t('observability.phase.columns.carryCost') }} ↓
760
- </span>
761
- </th>
762
- </tr>
763
- </thead>
764
- <tbody>
765
- <tr v-for="p in phaseRows" :key="p.phase" class="border-t border-default/70">
766
- <td class="py-1.5 pe-3 text-default">
767
- <span :class="p.phase ? '' : 'text-muted italic'">
768
- {{ phaseLabel(p.phase) }}
769
- </span>
770
- <span v-if="!p.phase" class="ms-1.5 text-[11px] text-app-600">
771
- {{ t('observability.phase.unattributedHint') }}
772
- </span>
773
- <UBadge
774
- v-if="p.errors"
775
- color="error"
776
- variant="subtle"
777
- size="sm"
778
- class="ms-2"
779
- >
780
- {{ t('observability.metricsBar.errors', { count: p.errors }, p.errors) }}
781
- </UBadge>
782
- </td>
783
- <td class="py-1.5 px-3 text-end tabular-nums text-toned">
784
- {{ p.calls }}
785
- </td>
786
- <td class="py-1.5 px-3 text-end tabular-nums text-toned">
787
- {{ formatTokens(totalInputTokens(p)) }}↑
788
- {{ formatTokens(p.completionTokens) }}↓
789
- </td>
790
- <td v-if="showCost" class="py-1.5 px-3 text-end tabular-nums text-toned">
791
- <!-- An em dash, not 0: this phase's model had no rate, and a zero here
792
- would read as a phase that cost nothing. -->
793
- {{ formatCost(p.costEstimate, costCurrency) ?? '—' }}
794
- </td>
795
- <td class="py-1.5 ps-3 text-end tabular-nums text-toned">
796
- {{ formatTokens(p.carryCostTokens) }}
797
- <span v-if="carryShare(p.carryCostTokens) !== null" class="text-app-600">
798
- · {{ carryShare(p.carryCostTokens) }}%
799
- </span>
800
- </td>
801
- </tr>
802
- </tbody>
803
- </table>
743
+ <UTable
744
+ :data="phaseRows"
745
+ :columns="phaseColumns"
746
+ :sorting="PHASE_SORTING"
747
+ :sorting-options="PHASE_SORTING_OPTIONS"
748
+ :ui="{
749
+ base: 'min-w-[32rem] text-xs',
750
+ th: 'px-3 py-1.5',
751
+ td: 'px-3 py-1.5 text-xs whitespace-normal',
752
+ }"
753
+ >
754
+ <template #phase-header>
755
+ {{ t('observability.phase.columns.phase') }}
756
+ </template>
757
+ <template #turns-header>
758
+ <span class="block text-end">
759
+ {{ t('observability.phase.columns.turns') }}
760
+ </span>
761
+ </template>
762
+ <template #tokens-header>
763
+ <span class="block text-end">
764
+ {{ t('observability.phase.columns.tokensInOut') }}
765
+ </span>
766
+ </template>
767
+ <template #cost-header>
768
+ <span class="block text-end" :title="t('observability.phase.costHint')">
769
+ {{ t('observability.phase.columns.cost') }}
770
+ </span>
771
+ </template>
772
+ <!-- The sort key, MARKED as one. Rows lead with carry cost rather than
773
+ with tokens, and the two orders genuinely differ: a phase that runs
774
+ late carries almost nothing however much it spent (nothing after it
775
+ re-sends its context). Leaving that implicit invites reading row 1
776
+ as "the phase that burned the most", which is the neighbouring
777
+ column. -->
778
+ <template #carryCost-header>
779
+ <span class="block text-end" :title="t('observability.phase.carryCostHint')">
780
+ {{ t('observability.phase.columns.carryCost') }} &darr;
781
+ </span>
782
+ </template>
783
+ <template #phase-cell="{ row }">
784
+ <span :class="row.original.phase ? '' : 'text-muted italic'">
785
+ {{ phaseLabel(row.original.phase) }}
786
+ </span>
787
+ <span v-if="!row.original.phase" class="ms-1.5 text-2xs text-app-600">
788
+ {{ t('observability.phase.unattributedHint') }}
789
+ </span>
790
+ <UBadge
791
+ v-if="row.original.errors"
792
+ color="error"
793
+ variant="subtle"
794
+ size="sm"
795
+ class="ms-2"
796
+ >
797
+ {{
798
+ t(
799
+ 'observability.metricsBar.errors',
800
+ { count: row.original.errors },
801
+ row.original.errors,
802
+ )
803
+ }}
804
+ </UBadge>
805
+ </template>
806
+ <template #turns-cell="{ row }">
807
+ <span class="block text-end tabular-nums text-toned">{{
808
+ row.original.calls
809
+ }}</span>
810
+ </template>
811
+ <template #tokens-cell="{ row }">
812
+ <span class="block text-end tabular-nums text-toned">
813
+ {{ formatTokens(totalInputTokens(row.original)) }}&uarr;
814
+ {{ formatTokens(row.original.completionTokens) }}&darr;
815
+ </span>
816
+ </template>
817
+ <!-- An em dash, not 0: this phase's model had no rate, and a zero here
818
+ would read as a phase that cost nothing. -->
819
+ <template #cost-cell="{ row }">
820
+ <span class="block text-end tabular-nums text-toned">
821
+ {{ formatCost(row.original.costEstimate, costCurrency) ?? '—' }}
822
+ </span>
823
+ </template>
824
+ <template #carryCost-cell="{ row }">
825
+ <span class="block text-end tabular-nums text-toned">
826
+ {{ formatTokens(row.original.carryCostTokens) }}
827
+ <span
828
+ v-if="carryShare(row.original.carryCostTokens) !== null"
829
+ class="text-app-600"
830
+ >
831
+ · {{ carryShare(row.original.carryCostTokens) }}%
832
+ </span>
833
+ </span>
834
+ </template>
835
+ </UTable>
804
836
  </div>
805
837
  </section>
806
838
 
@@ -838,9 +870,9 @@ function exportJson() {
838
870
  <!-- per-call list, narrowable by outcome -->
839
871
  <template v-else>
840
872
  <div class="flex flex-wrap items-center justify-between gap-2">
841
- <h2 class="text-[11px] uppercase tracking-wide text-dimmed">
873
+ <SectionLabel as="h2">
842
874
  {{ t('observability.callsTitle') }}
843
- </h2>
875
+ </SectionLabel>
844
876
  <OutcomeFilterChips v-model="callFilter" :options="callFilterOptions" />
845
877
  </div>
846
878
 
@@ -862,7 +894,9 @@ function exportJson() {
862
894
  class="overflow-hidden rounded-xl border border-default bg-default/40"
863
895
  :class="!c.ok ? 'border-app-error-900/60' : ''"
864
896
  >
865
- <button
897
+ <UButton
898
+ color="neutral"
899
+ variant="ghost"
866
900
  class="flex w-full items-center gap-3 px-4 py-2.5 text-start transition hover:bg-default/70"
867
901
  @click="toggle(c)"
868
902
  >
@@ -876,11 +910,8 @@ function exportJson() {
876
910
  class="h-4 w-4 shrink-0"
877
911
  :style="{ color: agentMeta(c.agentKind).color }"
878
912
  />
879
- <span class="text-[13px] text-default">{{ agentMeta(c.agentKind).label }}</span>
880
- <span
881
- class="hidden truncate text-[11px] text-dimmed sm:inline"
882
- :title="c.model"
883
- >
913
+ <span class="text-sm text-default">{{ agentMeta(c.agentKind).label }}</span>
914
+ <span class="hidden truncate text-2xs text-dimmed sm:inline" :title="c.model">
884
915
  {{ c.provider }}:{{ c.model }}
885
916
  <!-- Which upstream a GATEWAY routed to. Without it every `openrouter` row
886
917
  reads alike, and an upstream having a bad day cannot be told from the
@@ -890,9 +921,7 @@ function exportJson() {
890
921
  {{ t('observability.call.viaUpstream', { upstream: c.upstreamProvider }) }}
891
922
  </span>
892
923
  </span>
893
- <div
894
- class="ms-auto flex items-center gap-2.5 text-[11px] tabular-nums text-muted"
895
- >
924
+ <div class="ms-auto flex items-center gap-2.5 text-2xs tabular-nums text-muted">
896
925
  <span
897
926
  :title="
898
927
  t('observability.call.tokensTitle', {
@@ -939,13 +968,13 @@ function exportJson() {
939
968
  }}</span>
940
969
  <span class="hidden text-app-600 md:inline">{{ clock(c.createdAt) }}</span>
941
970
  </div>
942
- </button>
971
+ </UButton>
943
972
 
944
973
  <div v-if="expanded[c.id]" class="border-t border-default px-4 py-3 space-y-3">
945
- <p v-if="c.errorMessage" class="text-[12px] text-app-error-400">
974
+ <p v-if="c.errorMessage" class="text-xs text-app-error-400">
946
975
  {{ c.errorMessage }}
947
976
  </p>
948
- <div class="flex flex-wrap gap-x-5 gap-y-1 text-[11px] text-dimmed">
977
+ <div class="flex flex-wrap gap-x-5 gap-y-1 text-2xs text-dimmed">
949
978
  <span>{{ t('observability.call.messages', { count: c.messageCount }) }}</span>
950
979
  <span>{{ t('observability.call.tools', { count: c.toolCount }) }}</span>
951
980
  <span>{{
@@ -982,9 +1011,7 @@ function exportJson() {
982
1011
  </span>
983
1012
  </div>
984
1013
  <div>
985
- <div
986
- class="mb-1 flex items-center gap-2 text-[11px] uppercase tracking-wide text-dimmed"
987
- >
1014
+ <SectionLabel class="mb-1 flex items-center gap-2">
988
1015
  <span>{{ t('observability.call.prompt') }}</span>
989
1016
  <span
990
1017
  v-if="c.promptPrefixCount > 0"
@@ -996,25 +1023,25 @@ function exportJson() {
996
1023
  })
997
1024
  }}
998
1025
  </span>
999
- </div>
1026
+ </SectionLabel>
1000
1027
  <pre
1001
- class="max-h-72 overflow-auto rounded-lg bg-app-950/70 p-3 text-[11px] leading-relaxed text-toned"
1028
+ class="max-h-72 overflow-auto rounded-lg bg-app-950/70 p-3 text-2xs leading-relaxed text-toned"
1002
1029
  >{{ prettyPrompt(c.promptText) }}</pre>
1003
1030
  </div>
1004
1031
  <div>
1005
- <div class="mb-1 text-[11px] uppercase tracking-wide text-dimmed">
1032
+ <SectionLabel class="mb-1">
1006
1033
  {{ t('observability.call.response') }}
1007
- </div>
1034
+ </SectionLabel>
1008
1035
  <pre
1009
- class="max-h-72 overflow-auto rounded-lg bg-app-950/70 p-3 text-[11px] leading-relaxed text-toned"
1036
+ class="max-h-72 overflow-auto rounded-lg bg-app-950/70 p-3 text-2xs leading-relaxed text-toned"
1010
1037
  >{{ c.responseText || '—' }}</pre>
1011
1038
  </div>
1012
1039
  <div v-if="c.reasoningText">
1013
- <div class="mb-1 text-[11px] uppercase tracking-wide text-dimmed">
1040
+ <SectionLabel class="mb-1">
1014
1041
  {{ t('observability.call.reasoning') }}
1015
- </div>
1042
+ </SectionLabel>
1016
1043
  <pre
1017
- class="max-h-72 overflow-auto rounded-lg bg-app-950/70 p-3 text-[11px] leading-relaxed text-muted"
1044
+ class="max-h-72 overflow-auto rounded-lg bg-app-950/70 p-3 text-2xs leading-relaxed text-muted"
1018
1045
  >{{ c.reasoningText }}</pre>
1019
1046
  </div>
1020
1047
  </div>
@@ -1039,7 +1066,7 @@ function exportJson() {
1039
1066
  so the survey's absence would otherwise read as a phase that used no tools. -->
1040
1067
  <p
1041
1068
  v-if="bootstrap?.monorepo"
1042
- class="rounded-lg border border-dashed border-default px-3 py-2 text-[12px] text-muted"
1069
+ class="rounded-lg border border-dashed border-default px-3 py-2 text-xs text-muted"
1043
1070
  >
1044
1071
  {{ t('observability.toolCalls.surveyReadsElsewhere') }}
1045
1072
  </p>
@@ -1094,7 +1121,9 @@ function exportJson() {
1094
1121
  :key="s.id"
1095
1122
  class="overflow-hidden rounded-xl border border-default bg-default/40"
1096
1123
  >
1097
- <button
1124
+ <UButton
1125
+ color="neutral"
1126
+ variant="ghost"
1098
1127
  class="flex w-full items-center gap-3 px-4 py-2.5 text-start transition hover:bg-default/70"
1099
1128
  @click="toggleCtx(s)"
1100
1129
  >
@@ -1108,13 +1137,11 @@ function exportJson() {
1108
1137
  class="h-4 w-4 shrink-0"
1109
1138
  :style="{ color: agentMeta(s.agentKind).color }"
1110
1139
  />
1111
- <span class="text-[13px] text-default">{{ agentMeta(s.agentKind).label }}</span>
1112
- <span v-if="s.model" class="hidden truncate text-[11px] text-dimmed sm:inline">
1140
+ <span class="text-sm text-default">{{ agentMeta(s.agentKind).label }}</span>
1141
+ <span v-if="s.model" class="hidden truncate text-2xs text-dimmed sm:inline">
1113
1142
  {{ s.model }}
1114
1143
  </span>
1115
- <div
1116
- class="ms-auto flex items-center gap-2.5 text-[11px] tabular-nums text-muted"
1117
- >
1144
+ <div class="ms-auto flex items-center gap-2.5 text-2xs tabular-nums text-muted">
1118
1145
  <span :title="t('observability.context.injectedFiles')">{{
1119
1146
  t('observability.context.filesCount', { count: s.contextFiles.length })
1120
1147
  }}</span>
@@ -1123,64 +1150,64 @@ function exportJson() {
1123
1150
  }}</span>
1124
1151
  <span class="hidden text-app-600 md:inline">{{ clock(s.createdAt) }}</span>
1125
1152
  </div>
1126
- </button>
1153
+ </UButton>
1127
1154
 
1128
1155
  <div v-if="expandedCtx[s.id]" class="border-t border-default px-4 py-3 space-y-3">
1129
1156
  <div>
1130
- <div class="mb-1 text-[11px] uppercase tracking-wide text-dimmed">
1157
+ <SectionLabel class="mb-1">
1131
1158
  {{ t('observability.context.systemPrompt') }}
1132
- </div>
1159
+ </SectionLabel>
1133
1160
  <pre
1134
- class="max-h-72 overflow-auto rounded-lg bg-app-950/70 p-3 text-[11px] leading-relaxed text-toned"
1161
+ class="max-h-72 overflow-auto rounded-lg bg-app-950/70 p-3 text-2xs leading-relaxed text-toned"
1135
1162
  >{{ s.systemPrompt || '—' }}</pre>
1136
1163
  </div>
1137
1164
  <div>
1138
- <div class="mb-1 text-[11px] uppercase tracking-wide text-dimmed">
1165
+ <SectionLabel class="mb-1">
1139
1166
  {{ t('observability.context.userPrompt') }}
1140
- </div>
1167
+ </SectionLabel>
1141
1168
  <pre
1142
- class="max-h-72 overflow-auto rounded-lg bg-app-950/70 p-3 text-[11px] leading-relaxed text-toned"
1169
+ class="max-h-72 overflow-auto rounded-lg bg-app-950/70 p-3 text-2xs leading-relaxed text-toned"
1143
1170
  >{{ s.userPrompt || '—' }}</pre>
1144
1171
  </div>
1145
1172
  <div v-if="s.fragments.length">
1146
- <div class="mb-1 text-[11px] uppercase tracking-wide text-dimmed">
1173
+ <SectionLabel class="mb-1">
1147
1174
  {{ t('observability.context.bestPracticeFragments') }}
1148
- </div>
1175
+ </SectionLabel>
1149
1176
  <div
1150
1177
  v-for="f in s.fragments"
1151
1178
  :key="f.id"
1152
1179
  class="mb-2 rounded-lg bg-app-950/70 p-3"
1153
1180
  >
1154
- <div class="mb-1 text-[11px] text-muted">{{ f.id }}</div>
1155
- <pre class="max-h-48 overflow-auto text-[11px] leading-relaxed text-toned">{{
1181
+ <div class="mb-1 text-2xs text-muted">{{ f.id }}</div>
1182
+ <pre class="max-h-48 overflow-auto text-2xs leading-relaxed text-toned">{{
1156
1183
  f.body
1157
1184
  }}</pre>
1158
1185
  </div>
1159
1186
  </div>
1160
1187
  <div v-if="s.contextFiles.length">
1161
- <div class="mb-1 text-[11px] uppercase tracking-wide text-dimmed">
1188
+ <SectionLabel class="mb-1">
1162
1189
  {{ t('observability.context.injectedFiles') }}
1163
- </div>
1190
+ </SectionLabel>
1164
1191
  <div
1165
1192
  v-for="file in s.contextFiles"
1166
1193
  :key="file.path"
1167
1194
  class="mb-2 rounded-lg bg-app-950/70 p-3"
1168
1195
  >
1169
- <div class="mb-1 text-[11px] text-muted">
1196
+ <div class="mb-1 text-2xs text-muted">
1170
1197
  {{ file.title }}
1171
1198
  <span class="text-app-600">· {{ file.path }}</span>
1172
1199
  </div>
1173
- <pre class="max-h-72 overflow-auto text-[11px] leading-relaxed text-toned">{{
1200
+ <pre class="max-h-72 overflow-auto text-2xs leading-relaxed text-toned">{{
1174
1201
  file.content
1175
1202
  }}</pre>
1176
1203
  </div>
1177
1204
  </div>
1178
1205
  <div>
1179
- <div class="mb-1 text-[11px] uppercase tracking-wide text-dimmed">
1206
+ <SectionLabel class="mb-1">
1180
1207
  {{ t('observability.context.details') }}
1181
- </div>
1208
+ </SectionLabel>
1182
1209
  <pre
1183
- class="max-h-48 overflow-auto rounded-lg bg-app-950/70 p-3 text-[11px] leading-relaxed text-muted"
1210
+ class="max-h-48 overflow-auto rounded-lg bg-app-950/70 p-3 text-2xs leading-relaxed text-muted"
1184
1211
  >{{ prettyExtras(s.extras) }}</pre>
1185
1212
  </div>
1186
1213
  </div>
@@ -1192,11 +1219,11 @@ function exportJson() {
1192
1219
  <!-- Availability header: a static per-run fact (not telemetry-gated). -->
1193
1220
  <section
1194
1221
  v-if="searchAvailability"
1195
- class="flex flex-wrap items-center gap-x-3 gap-y-1 rounded-xl border border-default bg-default/50 px-4 py-3 text-[13px]"
1222
+ class="flex flex-wrap items-center gap-x-3 gap-y-1 rounded-xl border border-default bg-default/50 px-4 py-3 text-sm"
1196
1223
  >
1197
- <span class="text-[11px] uppercase tracking-wide text-dimmed">
1224
+ <SectionLabel as="span">
1198
1225
  {{ t('observability.webSearch') }}
1199
- </span>
1226
+ </SectionLabel>
1200
1227
  <span
1201
1228
  class="inline-flex items-center gap-1.5"
1202
1229
  :class="searchAvailability.available ? 'text-app-success-300' : 'text-muted'"
@@ -1232,9 +1259,9 @@ function exportJson() {
1232
1259
  </p>
1233
1260
 
1234
1261
  <div v-else>
1235
- <div class="mb-2 text-[11px] uppercase tracking-wide text-dimmed">
1262
+ <SectionLabel class="mb-2">
1236
1263
  {{ t('observability.search.queriesTitle') }}
1237
- </div>
1264
+ </SectionLabel>
1238
1265
  <ul class="space-y-2">
1239
1266
  <li
1240
1267
  v-for="q in searchQueries"
@@ -1247,12 +1274,10 @@ function exportJson() {
1247
1274
  :style="{ color: agentMeta(q.agentKind).color }"
1248
1275
  :title="agentMeta(q.agentKind).label"
1249
1276
  />
1250
- <span class="min-w-0 flex-1 truncate text-[13px] text-default" :title="q.query">
1277
+ <span class="min-w-0 flex-1 truncate text-sm text-default" :title="q.query">
1251
1278
  {{ q.query }}
1252
1279
  </span>
1253
- <div
1254
- class="flex shrink-0 items-center gap-2.5 text-[11px] tabular-nums text-muted"
1255
- >
1280
+ <div class="flex shrink-0 items-center gap-2.5 text-2xs tabular-nums text-muted">
1256
1281
  <span v-if="q.provider" class="hidden sm:inline">{{
1257
1282
  providerLabel(q.provider)
1258
1283
  }}</span>