@cat-factory/app 0.309.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 (133) hide show
  1. package/README.md +11 -2
  2. package/app/components/assistant/AssistantModal.vue +5 -4
  3. package/app/components/auth/BackendMisconfiguredScreen.vue +4 -3
  4. package/app/components/auth/LoginScreen.vue +40 -26
  5. package/app/components/auth/ResetPasswordScreen.vue +9 -4
  6. package/app/components/auth/UserMenu.vue +4 -3
  7. package/app/components/binaryCandidates/BinaryCandidatesWindow.vue +34 -23
  8. package/app/components/binaryOutput/StoredAssetView.vue +8 -6
  9. package/app/components/board/AddTaskModal.vue +16 -15
  10. package/app/components/board/AgentFailureCard.vue +12 -9
  11. package/app/components/board/AgentFailureHistory.vue +18 -9
  12. package/app/components/board/CreateInitiativeModal.vue +5 -4
  13. package/app/components/board/FailureDetail.vue +22 -12
  14. package/app/components/board/ReviewFrictionDialog.vue +4 -3
  15. package/app/components/board/nodes/BlockNode.vue +17 -15
  16. package/app/components/board/nodes/DecisionBadge.vue +4 -3
  17. package/app/components/board/nodes/FrameSwimlanes.vue +4 -3
  18. package/app/components/board/nodes/TaskCard.vue +12 -7
  19. package/app/components/board/nodes/TaskPipelineMini.vue +9 -7
  20. package/app/components/bootstrap/AdoptionReviewModal.vue +30 -21
  21. package/app/components/bootstrap/BootstrapModal.vue +3 -3
  22. package/app/components/brainstorm/BrainstormWindow.vue +5 -3
  23. package/app/components/bugFishing/BugFishingWindow.vue +49 -27
  24. package/app/components/clarity/ClarityReviewWindow.vue +5 -3
  25. package/app/components/common/CopyButton.vue +12 -11
  26. package/app/components/common/IconButton.vue +34 -9
  27. package/app/components/context/ContextAttachmentFields.vue +23 -12
  28. package/app/components/documents/ContextDocumentPicker.vue +12 -9
  29. package/app/components/documents/DocumentImportModal.vue +4 -3
  30. package/app/components/documents/DocumentOriginLink.vue +12 -9
  31. package/app/components/documents/DocumentSyncState.vue +3 -3
  32. package/app/components/documents/RepoContextDocPicker.vue +4 -3
  33. package/app/components/environments/EnvironmentStatusPanel.vue +4 -3
  34. package/app/components/followUp/FollowUpWindow.vue +9 -6
  35. package/app/components/forkDecision/ForkDecisionWindow.vue +71 -80
  36. package/app/components/foundational/FoundationalServiceCatalogList.vue +3 -2
  37. package/app/components/foundational/FoundationalServiceRegistry.vue +7 -6
  38. package/app/components/foundational/FoundationalServiceSources.vue +7 -6
  39. package/app/components/fragments/FragmentLibraryManager.vue +5 -4
  40. package/app/components/fragments/FragmentSelector.vue +12 -9
  41. package/app/components/gates/GateFailingCheckList.vue +4 -3
  42. package/app/components/gates/GateResultView.vue +17 -12
  43. package/app/components/github/AddServiceFromRepoModal.vue +11 -7
  44. package/app/components/github/GitHubOnboarding.vue +7 -2
  45. package/app/components/github/GitHubPanel.vue +9 -6
  46. package/app/components/github/RepoTreeBrowser.vue +15 -12
  47. package/app/components/humanTest/HumanTestWindow.vue +13 -9
  48. package/app/components/initiative/InitiativePlanReview.vue +28 -18
  49. package/app/components/initiative/InitiativeTrackerWindow.vue +171 -124
  50. package/app/components/judge/JudgeResultView.vue +5 -3
  51. package/app/components/kaizen/KaizenPanel.vue +67 -53
  52. package/app/components/layout/AccountTeamSettings.vue +9 -9
  53. package/app/components/layout/AppearanceSwitcher.vue +4 -3
  54. package/app/components/layout/BoardSwitcher.vue +10 -8
  55. package/app/components/layout/BoardToolbar.vue +5 -4
  56. package/app/components/layout/CommandBar.vue +4 -3
  57. package/app/components/layout/DefaultTestEnvBanner.vue +4 -3
  58. package/app/components/layout/IntegrationsHub.vue +8 -6
  59. package/app/components/layout/LanguageSwitcher.vue +4 -3
  60. package/app/components/layout/ModelProvidersHub.vue +4 -3
  61. package/app/components/layout/NotificationsInbox.vue +40 -30
  62. package/app/components/layout/PersonalSetupModal.vue +4 -3
  63. package/app/components/layout/RolePrompt.vue +4 -3
  64. package/app/components/layout/SideBar.vue +4 -3
  65. package/app/components/layout/TranslationWarningBanner.vue +4 -3
  66. package/app/components/layout/UiModeSwitcher.vue +8 -6
  67. package/app/components/layout/UiRoleSwitcher.vue +8 -6
  68. package/app/components/layout/WorkspaceMembersSettings.vue +2 -2
  69. package/app/components/media/ArtifactLightbox.vue +69 -39
  70. package/app/components/media/ImageCompare.vue +69 -74
  71. package/app/components/observability/OutcomeFilterChips.vue +5 -4
  72. package/app/components/observability/RunFailureSummary.vue +8 -6
  73. package/app/components/observability/StepModelActivity.vue +5 -3
  74. package/app/components/observability/ToolCallList.vue +4 -2
  75. package/app/components/outcome/OutcomeSummaryWindow.vue +5 -4
  76. package/app/components/palettes/AgentPalette.vue +18 -14
  77. package/app/components/panels/AgentStepDetail.vue +30 -19
  78. package/app/components/panels/ObservabilityPanel.vue +149 -114
  79. package/app/components/panels/OperatorDashboardPanel.vue +99 -91
  80. package/app/components/panels/ReportsPanel.vue +64 -61
  81. package/app/components/panels/ResultWindowShell.vue +148 -134
  82. package/app/components/panels/StepContainerStatus.vue +9 -9
  83. package/app/components/panels/StepDelegatedStatus.vue +11 -9
  84. package/app/components/panels/StepRestartControl.vue +3 -2
  85. package/app/components/panels/inspector/EpicChildren.vue +4 -3
  86. package/app/components/panels/inspector/FrontendConfig.vue +3 -2
  87. package/app/components/panels/inspector/InspectorSection.vue +22 -17
  88. package/app/components/panels/inspector/ServiceConnections.vue +3 -2
  89. package/app/components/panels/inspector/ServiceTestConfig.vue +5 -4
  90. package/app/components/panels/inspector/TaskExecution.vue +5 -4
  91. package/app/components/panels/inspector/TaskRunSettings.vue +10 -6
  92. package/app/components/pipeline/EstimateThresholdFields.vue +13 -10
  93. package/app/components/pipeline/GateConfigFields.vue +14 -18
  94. package/app/components/pipeline/PipelineBuilder.vue +90 -77
  95. package/app/components/pipeline/PipelinePicker.vue +8 -6
  96. package/app/components/pipeline/PipelineProgress.vue +26 -21
  97. package/app/components/prReview/PrReviewWindow.vue +46 -25
  98. package/app/components/providers/ApiKeysSection.vue +4 -3
  99. package/app/components/ralph/RalphLoopResultView.vue +4 -3
  100. package/app/components/requirements/RequirementsReviewWindow.vue +10 -6
  101. package/app/components/riskPolicy/RiskPolicyPicker.vue +8 -6
  102. package/app/components/sandbox/SandboxPanel.vue +73 -54
  103. package/app/components/settings/CloudflareHandlerSection.vue +33 -30
  104. package/app/components/settings/ConsensusGroupsSection.vue +40 -30
  105. package/app/components/settings/InfraHandlersConfigurator.vue +16 -13
  106. package/app/components/settings/InfrastructureBackendPicker.vue +17 -17
  107. package/app/components/settings/IssueTrackerPanel.vue +21 -6
  108. package/app/components/settings/LocalModelEndpointsPanel.vue +3 -2
  109. package/app/components/settings/ModelConfigurationPanel.vue +9 -8
  110. package/app/components/settings/OpenRouterCatalogPanel.vue +4 -3
  111. package/app/components/settings/ProviderPreferenceEditor.vue +5 -6
  112. package/app/components/settings/RiskPolicyEditorRow.vue +3 -2
  113. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +10 -8
  114. package/app/components/skills/ReviewSkillQueue.vue +4 -3
  115. package/app/components/skills/SkillLibraryManager.vue +7 -6
  116. package/app/components/slack/SlackPanel.vue +9 -5
  117. package/app/components/spec/ServiceSpecWindow.vue +4 -3
  118. package/app/components/tasks/ContextIssuePicker.vue +12 -9
  119. package/app/components/tasks/TaskContextIssues.vue +4 -3
  120. package/app/components/testing/TestReportWindow.vue +14 -8
  121. package/app/components/theme/ThemeImportModal.vue +2 -2
  122. package/app/components/visualConfirm/VisualConfirmationWindow.vue +62 -35
  123. package/i18n/locales/de.json +7 -3
  124. package/i18n/locales/en.json +7 -3
  125. package/i18n/locales/es.json +7 -3
  126. package/i18n/locales/fr.json +7 -3
  127. package/i18n/locales/he.json +7 -3
  128. package/i18n/locales/it.json +7 -3
  129. package/i18n/locales/ja.json +7 -3
  130. package/i18n/locales/pl.json +7 -3
  131. package/i18n/locales/tr.json +7 -3
  132. package/i18n/locales/uk.json +7 -3
  133. package/package.json +1 -1
@@ -9,6 +9,7 @@
9
9
  import { computed, ref, watch } from 'vue'
10
10
  import { useModalBehavior } from '@modular-vue/core'
11
11
  import type { ArtifactBlobs } from '~/composables/useArtifactBlobs'
12
+ import IconButton from '~/components/common/IconButton.vue'
12
13
 
13
14
  interface LightboxItem {
14
15
  artifactId: string
@@ -180,39 +181,56 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
180
181
  {{ t('media.lightbox.counter', { current: index + 1, total }) }}
181
182
  </span>
182
183
  <div class="flex shrink-0 items-center gap-1">
183
- <button
184
- class="rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default disabled:opacity-40"
185
- :title="t('media.lightbox.zoomOut')"
184
+ <IconButton
185
+ color="neutral"
186
+ variant="ghost"
187
+ icon="i-lucide-zoom-out"
188
+ :label="t('media.lightbox.zoomOut')"
189
+ :ui="{
190
+ base: 'rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default disabled:opacity-40',
191
+ leadingIcon: 'h-4 w-4',
192
+ }"
186
193
  :disabled="scale <= MIN_SCALE"
187
194
  @click="zoomBy(1 / 1.25)"
188
- >
189
- <UIcon name="i-lucide-zoom-out" class="h-4 w-4" />
190
- </button>
195
+ />
191
196
  <span class="w-10 text-center text-2xs tabular-nums text-dimmed">{{
192
197
  n(scale, 'percent')
193
198
  }}</span>
194
- <button
195
- class="rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default disabled:opacity-40"
196
- :title="t('media.lightbox.zoomIn')"
199
+ <IconButton
200
+ color="neutral"
201
+ variant="ghost"
202
+ icon="i-lucide-zoom-in"
203
+ :label="t('media.lightbox.zoomIn')"
204
+ :ui="{
205
+ base: 'rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default disabled:opacity-40',
206
+ leadingIcon: 'h-4 w-4',
207
+ }"
197
208
  :disabled="scale >= MAX_SCALE"
198
209
  @click="zoomBy(1.25)"
199
- >
200
- <UIcon name="i-lucide-zoom-in" class="h-4 w-4" />
201
- </button>
202
- <button
203
- class="rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default"
204
- :title="t('media.lightbox.reset')"
210
+ />
211
+ <IconButton
212
+ color="neutral"
213
+ variant="ghost"
214
+ icon="i-lucide-maximize"
215
+ :label="t('media.lightbox.reset')"
216
+ :ui="{
217
+ base: 'rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default',
218
+ leadingIcon: 'h-4 w-4',
219
+ }"
205
220
  @click="resetView"
206
- >
207
- <UIcon name="i-lucide-maximize" class="h-4 w-4" />
208
- </button>
209
- <button
210
- class="ms-1 rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default"
211
- :title="t('media.lightbox.close')"
221
+ />
222
+ <IconButton
223
+ color="neutral"
224
+ variant="ghost"
225
+ icon="i-lucide-x"
226
+ class="ms-1"
227
+ :label="t('media.lightbox.close')"
228
+ :ui="{
229
+ base: 'rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default',
230
+ leadingIcon: 'h-4 w-4',
231
+ }"
212
232
  @click="close"
213
- >
214
- <UIcon name="i-lucide-x" class="h-4 w-4" />
215
- </button>
233
+ />
216
234
  </div>
217
235
  </div>
218
236
 
@@ -222,14 +240,19 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
222
240
  @wheel="onWheel"
223
241
  @click.self="close"
224
242
  >
225
- <button
243
+ <IconButton
226
244
  v-if="total > 1"
227
- class="absolute start-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-default/80 p-2 text-toned hover:bg-elevated hover:text-highlighted"
228
- :title="t('media.lightbox.prev')"
245
+ color="neutral"
246
+ variant="ghost"
247
+ icon="i-lucide-chevron-left"
248
+ class="absolute start-3 top-1/2 z-10 -translate-y-1/2"
249
+ :label="t('media.lightbox.prev')"
250
+ :ui="{
251
+ base: 'rounded-full bg-default/80 p-2 text-toned hover:bg-elevated hover:text-highlighted',
252
+ leadingIcon: 'h-5 w-5 rtl:-scale-x-100',
253
+ }"
229
254
  @click="go(-1)"
230
- >
231
- <UIcon name="i-lucide-chevron-left" class="h-5 w-5 rtl:-scale-x-100" />
232
- </button>
255
+ />
233
256
 
234
257
  <img
235
258
  v-if="url"
@@ -257,23 +280,30 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
257
280
  <p class="text-xs">
258
281
  {{ state === 'error' ? t('media.lightbox.failed') : t('media.lightbox.loading') }}
259
282
  </p>
260
- <button
283
+ <UButton
284
+ color="neutral"
285
+ variant="link"
261
286
  v-if="state === 'error' && current"
262
- class="text-xs text-app-warning-300 hover:underline"
287
+ class="p-0 text-xs text-app-warning-300 hover:underline"
263
288
  @click="props.blobs.retry(current.artifactId)"
264
289
  >
265
290
  {{ t('common.retry') }}
266
- </button>
291
+ </UButton>
267
292
  </div>
268
293
 
269
- <button
294
+ <IconButton
270
295
  v-if="total > 1"
271
- class="absolute end-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-default/80 p-2 text-toned hover:bg-elevated hover:text-highlighted"
272
- :title="t('media.lightbox.next')"
296
+ color="neutral"
297
+ variant="ghost"
298
+ icon="i-lucide-chevron-right"
299
+ class="absolute end-3 top-1/2 z-10 -translate-y-1/2"
300
+ :label="t('media.lightbox.next')"
301
+ :ui="{
302
+ base: 'rounded-full bg-default/80 p-2 text-toned hover:bg-elevated hover:text-highlighted',
303
+ leadingIcon: 'h-5 w-5 rtl:-scale-x-100',
304
+ }"
273
305
  @click="go(1)"
274
- >
275
- <UIcon name="i-lucide-chevron-right" class="h-5 w-5 rtl:-scale-x-100" />
276
- </button>
306
+ />
277
307
  </div>
278
308
  </div>
279
309
  </Teleport>
@@ -9,6 +9,7 @@
9
9
  // as a drag-and-drop / click upload target (emits `uploadReference`).
10
10
  import { computed, nextTick, ref, watch } from 'vue'
11
11
  import type { ArtifactBlobs } from '~/composables/useArtifactBlobs'
12
+ import IconButton from '~/components/common/IconButton.vue'
12
13
 
13
14
  const props = defineProps<{
14
15
  view: string
@@ -135,23 +136,18 @@ async function renderDiff() {
135
136
  watch([mode, actualUrl, refUrl], renderDiff, { immediate: true })
136
137
 
137
138
  // --- reference upload (drag-drop + click) ---
138
- const dragOver = ref(false)
139
- const refInput = ref<HTMLInputElement | null>(null)
140
- // Accept only the formats the file input advertises (PNG/JPEG), so a dropped GIF/SVG/WebP
141
- // can't slip past the picker's `accept` filter.
139
+ // `UFileUpload` owns the picker, the drop zone and the drag state; the click-to-replace
140
+ // affordance calls its `open()`. The type guard stays ours: `accept` filters the PICKER, and a
141
+ // dropped GIF/SVG/WebP would otherwise arrive anyway.
142
142
  const ACCEPTED = /^image\/(png|jpeg)$/
143
- function pickFile(files: FileList | null | undefined) {
144
- const file = files?.[0]
143
+ const pendingRef = ref<File | null>(null)
144
+ watch(pendingRef, (file) => {
145
145
  if (file && ACCEPTED.test(file.type)) emit('uploadReference', file)
146
- }
147
- function onDrop(e: DragEvent) {
148
- dragOver.value = false
149
- pickFile(e.dataTransfer?.files)
150
- }
151
- function onRefInput(e: Event) {
152
- pickFile((e.target as HTMLInputElement).files)
153
- if (refInput.value) refInput.value.value = ''
154
- }
146
+ // Cleared either way, so a fresh pick is a fresh file. `UFileUpload` carries `reset` for the
147
+ // other half of this: clearing the model alone leaves the native input holding the file it
148
+ // already has, and the browser fires no change event when the same one is chosen twice.
149
+ if (file) pendingRef.value = null
150
+ })
155
151
  </script>
156
152
 
157
153
  <template>
@@ -163,16 +159,23 @@ function onRefInput(e: Event) {
163
159
  v-if="MODES.length > 1"
164
160
  class="flex items-center gap-0.5 rounded-md border border-default bg-app-950/60 p-0.5"
165
161
  >
166
- <button
162
+ <IconButton
167
163
  v-for="m in MODES"
168
164
  :key="m.id"
169
- class="rounded-sm px-1.5 py-1 text-muted hover:text-default"
170
- :class="mode === m.id ? 'bg-elevated text-app-100' : ''"
171
- :title="m.label"
165
+ color="neutral"
166
+ variant="ghost"
167
+ :icon="m.icon"
168
+ :label="m.label"
169
+ :aria-pressed="mode === m.id"
170
+ :ui="{
171
+ base: [
172
+ 'rounded-sm px-1.5 py-1 text-muted hover:bg-transparent hover:text-default',
173
+ mode === m.id ? 'bg-elevated text-app-100' : '',
174
+ ].join(' '),
175
+ leadingIcon: 'h-3.5 w-3.5',
176
+ }"
172
177
  @click="mode = m.id"
173
- >
174
- <UIcon :name="m.icon" class="h-3.5 w-3.5" />
175
- </button>
178
+ />
176
179
  </div>
177
180
  </div>
178
181
 
@@ -182,9 +185,11 @@ function onRefInput(e: Event) {
182
185
  <figcaption class="text-3xs uppercase tracking-wide text-dimmed">
183
186
  {{ t('media.compare.actual') }}
184
187
  </figcaption>
185
- <button
188
+ <UButton
186
189
  v-if="actualUrl"
187
- class="block w-full overflow-hidden rounded-sm border border-default hover:border-app-600"
190
+ color="neutral"
191
+ variant="ghost"
192
+ class="block w-full overflow-hidden rounded-sm border border-default p-0 hover:border-app-600 hover:bg-transparent"
188
193
  @click="actualId && emit('expand', actualId)"
189
194
  >
190
195
  <img
@@ -192,7 +197,7 @@ function onRefInput(e: Event) {
192
197
  :alt="t('media.compare.actualAlt', { view })"
193
198
  class="w-full cursor-zoom-in"
194
199
  />
195
- </button>
200
+ </UButton>
196
201
  <div
197
202
  v-else
198
203
  class="flex h-32 items-center justify-center rounded-sm border border-dashed border-muted text-2xs text-app-600"
@@ -214,40 +219,39 @@ function onRefInput(e: Event) {
214
219
  {{ t('media.compare.fromLinkedDesign') }}
215
220
  </span>
216
221
  </figcaption>
217
- <button
218
- v-if="refUrl"
219
- class="group relative block w-full overflow-hidden rounded-sm border border-default hover:border-app-600"
220
- @click="referenceId && emit('expand', referenceId)"
222
+ <UFileUpload
223
+ v-model="pendingRef"
224
+ reset
225
+ accept="image/png,image/jpeg"
226
+ :disabled="busy"
227
+ :preview="false"
228
+ :label="t('media.compare.dropHint')"
229
+ icon="i-lucide-image-up"
230
+ :ui="{ base: 'h-32 text-2xs' }"
221
231
  >
222
- <img
223
- :src="refUrl"
224
- :alt="t('media.compare.referenceAlt', { view })"
225
- class="w-full cursor-zoom-in"
226
- />
227
- <span
228
- class="absolute bottom-1 end-1 rounded-sm bg-app-950/80 px-1.5 py-0.5 text-3xs text-toned opacity-0 group-hover:opacity-100"
229
- @click.stop="refInput?.click()"
230
- >
231
- {{ t('media.compare.replace') }}
232
- </span>
233
- </button>
234
- <!-- Drop zone when no reference yet -->
235
- <div
236
- v-else
237
- class="flex h-32 cursor-pointer flex-col items-center justify-center gap-1 rounded-sm border border-dashed text-2xs transition"
238
- :class="
239
- dragOver
240
- ? 'border-app-warning-500 bg-app-warning-500/5 text-app-warning-300'
241
- : 'border-muted text-app-600 hover:border-app-500 hover:text-muted'
242
- "
243
- @click="refInput?.click()"
244
- @dragover.prevent="dragOver = true"
245
- @dragleave.prevent="dragOver = false"
246
- @drop.prevent="onDrop"
247
- >
248
- <UIcon name="i-lucide-image-up" class="h-5 w-5" />
249
- <span>{{ t('media.compare.dropHint') }}</span>
250
- </div>
232
+ <!-- With a reference already in place the tile IS the surface: it opens the lightbox,
233
+ and the hover affordance opens the picker the same drop zone would. -->
234
+ <template v-if="refUrl" #default="{ open }">
235
+ <UButton
236
+ variant="ghost"
237
+ color="neutral"
238
+ class="group relative block w-full overflow-hidden rounded-sm border border-default p-0 hover:border-app-600 hover:bg-transparent"
239
+ @click="referenceId && emit('expand', referenceId)"
240
+ >
241
+ <img
242
+ :src="refUrl"
243
+ :alt="t('media.compare.referenceAlt', { view })"
244
+ class="w-full cursor-zoom-in"
245
+ />
246
+ <span
247
+ class="absolute bottom-1 end-1 rounded-sm bg-app-950/80 px-1.5 py-0.5 text-3xs text-toned opacity-0 group-hover:opacity-100"
248
+ @click.stop="open()"
249
+ >
250
+ {{ t('media.compare.replace') }}
251
+ </span>
252
+ </UButton>
253
+ </template>
254
+ </UFileUpload>
251
255
  </figure>
252
256
  </div>
253
257
 
@@ -265,12 +269,13 @@ function onRefInput(e: Event) {
265
269
  </div>
266
270
  <div class="flex items-center gap-2 text-2xs uppercase tracking-wide text-muted">
267
271
  <span>{{ t('media.compare.reference') }}</span>
268
- <input
269
- v-model.number="overlayOpacity"
270
- type="range"
271
- min="0"
272
- max="100"
273
- class="flex-1 accent-app-warning-500"
272
+ <USlider
273
+ v-model="overlayOpacity"
274
+ :min="0"
275
+ :max="100"
276
+ color="warning"
277
+ size="xs"
278
+ class="flex-1"
274
279
  />
275
280
  <span>{{ t('media.compare.actual') }}</span>
276
281
  </div>
@@ -326,15 +331,5 @@ function onRefInput(e: Event) {
326
331
  <canvas ref="diffCanvas" :class="DIFF_CANVAS_CLASS" />
327
332
  <p class="text-3xs text-dimmed">{{ t('media.compare.diffHint') }}</p>
328
333
  </div>
329
-
330
- <!-- Hidden file input shared by replace/drop zone -->
331
- <input
332
- ref="refInput"
333
- type="file"
334
- accept="image/png,image/jpeg"
335
- class="hidden"
336
- :disabled="busy"
337
- @change="onRefInput"
338
- />
339
334
  </div>
340
335
  </template>
@@ -25,17 +25,18 @@ function toneClass(option: (typeof props.options)[number], active: boolean): str
25
25
 
26
26
  <template>
27
27
  <div class="flex rounded-lg border border-default p-0.5 text-xs">
28
- <button
28
+ <UButton
29
+ color="neutral"
30
+ variant="ghost"
29
31
  v-for="option in options"
30
32
  :key="option.value"
31
- type="button"
32
- class="rounded-md px-2.5 py-1 transition"
33
+ class="rounded-md px-2.5 py-1 text-xs transition"
33
34
  :class="toneClass(option, option.value === modelValue)"
34
35
  :aria-pressed="option.value === modelValue"
35
36
  @click="emit('update:modelValue', option.value)"
36
37
  >
37
38
  {{ option.label }}
38
39
  <span class="tabular-nums opacity-70">{{ option.count }}</span>
39
- </button>
40
+ </UButton>
40
41
  </div>
41
42
  </template>
@@ -121,9 +121,10 @@ const earlierFailedToolCalls = computed(() =>
121
121
  <!-- The failing calls themselves. -->
122
122
  <div class="mt-3 space-y-2">
123
123
  <!-- Last model call that FAILED outright. -->
124
- <button
124
+ <UButton
125
+ color="neutral"
126
+ variant="ghost"
125
127
  v-if="evidence.lastErroredCall"
126
- type="button"
127
128
  class="flex w-full items-start gap-3 rounded-lg border border-app-error-900/50 bg-app-950/50 px-3 py-2 text-start transition hover:bg-default/70"
128
129
  @click="emit('showCall', evidence.lastErroredCall.id)"
129
130
  >
@@ -159,12 +160,13 @@ const earlierFailedToolCalls = computed(() =>
159
160
  </p>
160
161
  </div>
161
162
  <UIcon name="i-lucide-chevron-right" class="mt-0.5 h-4 w-4 shrink-0 text-app-600" />
162
- </button>
163
+ </UButton>
163
164
 
164
165
  <!-- Last TOOL call that failed: the row no rollup counts. -->
165
- <button
166
+ <UButton
167
+ color="neutral"
168
+ variant="ghost"
166
169
  v-if="evidence.lastFailedToolCall"
167
- type="button"
168
170
  class="flex w-full items-start gap-3 rounded-lg border border-app-error-900/50 bg-app-950/50 px-3 py-2 text-start transition hover:bg-default/70"
169
171
  @click="emit('showFailingTools')"
170
172
  >
@@ -208,7 +210,7 @@ const earlierFailedToolCalls = computed(() =>
208
210
  </p>
209
211
  </div>
210
212
  <UIcon name="i-lucide-chevron-right" class="mt-0.5 h-4 w-4 shrink-0 text-app-600" />
211
- </button>
213
+ </UButton>
212
214
 
213
215
  <!-- Nothing failing to point at. Which of the reasons it is decides what an operator should
214
216
  do next, so each gets its own sentence rather than one shared shrug. `emptyReason` is
@@ -35,13 +35,15 @@ function openObservability() {
35
35
  <SectionLabel as="span">
36
36
  {{ t('observability.modelActivity') }}
37
37
  </SectionLabel>
38
- <button
38
+ <UButton
39
+ color="neutral"
40
+ variant="ghost"
39
41
  v-if="instanceId"
40
- class="text-2xs text-app-info-400 hover:text-app-info-300"
42
+ class="p-0 text-2xs text-app-info-400 hover:bg-transparent hover:text-app-info-300"
41
43
  @click="openObservability"
42
44
  >
43
45
  {{ t('observability.viewAllCalls') }}
44
- </button>
46
+ </UButton>
45
47
  </div>
46
48
  <StepMetricsBar
47
49
  v-if="hasCalls && metrics"
@@ -211,7 +211,9 @@ function prettyArgs(raw: string): string {
211
211
  class="overflow-hidden rounded-xl border border-default bg-default/40"
212
212
  :class="!call.ok ? 'border-app-error-900/60' : ''"
213
213
  >
214
- <button
214
+ <UButton
215
+ color="neutral"
216
+ variant="ghost"
215
217
  class="flex w-full items-center gap-3 px-4 py-2.5 text-start transition hover:bg-default/70"
216
218
  @click="toggle(call)"
217
219
  >
@@ -236,7 +238,7 @@ function prettyArgs(raw: string): string {
236
238
  </UBadge>
237
239
  <span class="hidden text-app-600 md:inline">{{ clock(call.startedAt) }}</span>
238
240
  </div>
239
- </button>
241
+ </UButton>
240
242
 
241
243
  <div v-if="expanded[call.id]" class="border-t border-default px-4 py-3 space-y-3">
242
244
  <div class="flex flex-wrap gap-x-5 gap-y-1 text-2xs text-dimmed">
@@ -693,11 +693,12 @@ function openTestReport() {
693
693
  }}
694
694
  </p>
695
695
  <div class="grid grid-cols-2 gap-2 sm:grid-cols-3">
696
- <button
696
+ <UButton
697
+ color="neutral"
698
+ variant="ghost"
697
699
  v-for="(view, position) in outcome.visuals.views"
698
700
  :key="`${position}:${view.view}`"
699
- type="button"
700
- class="group overflow-hidden rounded-md border border-default bg-app-950/60 text-start transition hover:border-app-600"
701
+ class="group block overflow-hidden rounded-md border border-default bg-app-950/60 hover:bg-app-950/60 focus-visible:bg-app-950/60 disabled:bg-app-950/60 p-0 text-start transition hover:border-app-600"
701
702
  :disabled="!view.artifactId"
702
703
  data-testid="outcome-shot"
703
704
  @click="openShot(view, position)"
@@ -730,7 +731,7 @@ function openTestReport() {
730
731
  />
731
732
  {{ view.view }}
732
733
  </span>
733
- </button>
734
+ </UButton>
734
735
  </div>
735
736
  </template>
736
737
  <template v-else>
@@ -74,24 +74,28 @@ function toggle(id: string) {
74
74
  </div>
75
75
  <div class="space-y-2">
76
76
  <section v-for="g in groups" :key="g.id">
77
- <SectionLabel
78
- as="button"
79
- type="button"
80
- class="flex w-full items-center gap-1.5 rounded-sm px-1 py-1 text-start transition hover:text-default"
77
+ <UButton
78
+ color="neutral"
79
+ variant="link"
80
+ class="group w-full rounded-sm px-1 py-1"
81
+ :aria-expanded="!isCollapsed(g.id)"
81
82
  @click="toggle(g.id)"
82
83
  >
83
- <UIcon
84
- :name="isCollapsed(g.id) ? 'i-lucide-chevron-right' : 'i-lucide-chevron-down'"
85
- class="h-3.5 w-3.5 shrink-0"
86
- />
87
- <span>{{ g.label }}</span>
88
- <span class="ms-auto text-app-600">{{ g.agents.length }}</span>
89
- </SectionLabel>
84
+ <SectionLabel as="span" class="flex w-full items-center gap-1.5 group-hover:text-default">
85
+ <UIcon
86
+ :name="isCollapsed(g.id) ? 'i-lucide-chevron-right' : 'i-lucide-chevron-down'"
87
+ class="h-3.5 w-3.5 shrink-0"
88
+ />
89
+ <span>{{ g.label }}</span>
90
+ <span class="ms-auto text-app-600">{{ g.agents.length }}</span>
91
+ </SectionLabel>
92
+ </UButton>
90
93
  <div v-if="!isCollapsed(g.id)" class="mt-1 space-y-1.5">
91
- <button
94
+ <UButton
95
+ color="neutral"
96
+ variant="ghost"
92
97
  v-for="a in g.agents"
93
98
  :key="a.kind"
94
- type="button"
95
99
  class="flex w-full items-center gap-2.5 rounded-lg border border-muted bg-elevated/60 p-2 text-start transition hover:border-app-500 hover:bg-elevated"
96
100
  :title="a.description"
97
101
  :data-testid="`palette-agent-${a.kind}`"
@@ -108,7 +112,7 @@ function toggle(id: string) {
108
112
  <div class="truncate text-3xs text-muted">{{ a.description }}</div>
109
113
  </div>
110
114
  <UIcon name="i-lucide-plus" class="ms-auto h-4 w-4 shrink-0 text-dimmed" />
111
- </button>
115
+ </UButton>
112
116
  </div>
113
117
  </section>
114
118
  </div>