@cat-factory/app 0.309.0 → 0.310.1

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 +2 -2
@@ -56,6 +56,8 @@ const ROLE_OPTIONS = computed<{ label: string; value: SlackMemberRole }[]>(() =>
56
56
  ])
57
57
 
58
58
  // Local editable copies, synced from the store on load.
59
+ const channelSuggestions = computed(() => slack.channels.map((ch) => `#${ch.name}`))
60
+
59
61
  const routes = reactive<Record<NotificationType, SlackRoute>>({
60
62
  merge_review: { enabled: false, channel: '' },
61
63
  pipeline_complete: { enabled: false, channel: '' },
@@ -278,18 +280,20 @@ async function saveMapping() {
278
280
  >
279
281
  <USwitch v-model="routes[row.type]!.enabled" size="sm" />
280
282
  <span class="w-32 text-sm text-toned">{{ row.label }}</span>
281
- <UInput
283
+ <!-- The workspace's channels are suggestions, not a closed list: a route may name
284
+ a channel the bot cannot enumerate yet. `mode="autocomplete"` is what makes the
285
+ typed name the value: the DEFAULT combobox mode writes its model only when
286
+ something is SELECTED, so a channel typed and then saved would be dropped. -->
287
+ <UInputMenu
282
288
  v-model="routes[row.type]!.channel"
289
+ mode="autocomplete"
290
+ :items="channelSuggestions"
283
291
  size="sm"
284
292
  class="flex-1"
285
293
  :placeholder="t('slack.routing.channelPlaceholder')"
286
294
  :disabled="!routes[row.type]!.enabled"
287
- list="slack-channels"
288
295
  />
289
296
  </div>
290
- <datalist id="slack-channels">
291
- <option v-for="ch in slack.channels" :key="ch.id" :value="`#${ch.name}`" />
292
- </datalist>
293
297
 
294
298
  <label class="flex items-center gap-2">
295
299
  <USwitch v-model="mentionsEnabled" size="sm" />
@@ -291,8 +291,9 @@ function kindLabel(item: RequirementItem): string {
291
291
  </SectionLabel>
292
292
  <ul class="space-y-0.5">
293
293
  <li v-for="(group, gi) in mod.groups ?? []" :key="gi">
294
- <button
295
- type="button"
294
+ <UButton
295
+ color="neutral"
296
+ variant="ghost"
296
297
  class="flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-start text-sm transition"
297
298
  :class="
298
299
  selected?.m === mi && selected?.g === gi
@@ -303,7 +304,7 @@ function kindLabel(item: RequirementItem): string {
303
304
  >
304
305
  <span class="truncate">{{ group.name }}</span>
305
306
  <span class="shrink-0 text-3xs text-dimmed">{{ reqCount(group) }}</span>
306
- </button>
307
+ </UButton>
307
308
  </li>
308
309
  <li
309
310
  v-if="(mod.groups?.length ?? 0) === 0"
@@ -339,10 +339,11 @@ onMounted(() => {
339
339
 
340
340
  <div class="max-h-56 space-y-0.5 overflow-y-auto">
341
341
  <!-- Already-imported issues (linked directly, no re-fetch). -->
342
- <button
342
+ <UButton
343
+ color="neutral"
344
+ variant="ghost"
343
345
  v-for="row in importedRows"
344
346
  :key="`imp:${row.externalId}`"
345
- type="button"
346
347
  class="flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-start text-xs text-toned hover:bg-elevated/70"
347
348
  @click="pickImported(row)"
348
349
  >
@@ -351,13 +352,14 @@ onMounted(() => {
351
352
  <UBadge color="neutral" variant="soft" size="xs" class="ms-auto shrink-0">{{
352
353
  t('tasks.picker.imported')
353
354
  }}</UBadge>
354
- </button>
355
+ </UButton>
355
356
 
356
357
  <!-- Tracker search hits (imported on add). -->
357
- <button
358
+ <UButton
359
+ color="neutral"
360
+ variant="ghost"
358
361
  v-for="r in searchRows"
359
362
  :key="`hit:${r.externalId}`"
360
- type="button"
361
363
  class="flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-start text-xs text-toned hover:bg-elevated/70"
362
364
  @click="pickSearch(r)"
363
365
  >
@@ -366,12 +368,13 @@ onMounted(() => {
366
368
  <UBadge v-if="r.status" color="neutral" variant="soft" size="xs" class="ms-auto shrink-0">
367
369
  {{ r.status }}
368
370
  </UBadge>
369
- </button>
371
+ </UButton>
370
372
 
371
373
  <!-- Explicit URL/key reference (imported on add). -->
372
- <button
374
+ <UButton
375
+ color="neutral"
376
+ variant="ghost"
373
377
  v-if="refRow"
374
- type="button"
375
378
  class="flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-start text-xs text-toned hover:bg-elevated/70"
376
379
  @click="pickRef(refRow)"
377
380
  >
@@ -383,7 +386,7 @@ onMounted(() => {
383
386
  </template>
384
387
  </i18n-t>
385
388
  </span>
386
- </button>
389
+ </UButton>
387
390
 
388
391
  <EmptyState
389
392
  v-if="empty"
@@ -132,10 +132,11 @@ async function attach(item: PendingContext) {
132
132
  />
133
133
 
134
134
  <div v-if="linked.length" class="space-y-1">
135
- <a
135
+ <ULink
136
+ raw
136
137
  v-for="issue in linked"
137
138
  :key="`${issue.source}:${issue.externalId}`"
138
- :href="issue.url"
139
+ :to="issue.url"
139
140
  target="_blank"
140
141
  rel="noopener"
141
142
  class="flex items-center gap-1.5 rounded-md border border-default bg-default/60 px-2 py-1.5 text-xs text-toned hover:bg-elevated/60"
@@ -148,7 +149,7 @@ async function attach(item: PendingContext) {
148
149
  <UBadge color="neutral" variant="soft" size="xs" class="ms-auto shrink-0">
149
150
  {{ issue.status }}
150
151
  </UBadge>
151
- </a>
152
+ </ULink>
152
153
  </div>
153
154
  <p v-else class="text-2xs text-dimmed">
154
155
  {{ emptyHint }}
@@ -672,7 +672,9 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
672
672
  :key="g.key"
673
673
  class="overflow-hidden rounded-lg border border-default bg-default/60"
674
674
  >
675
- <button
675
+ <UButton
676
+ color="neutral"
677
+ variant="ghost"
676
678
  class="flex w-full items-center gap-2 px-3 py-2 text-start hover:bg-elevated/40"
677
679
  @click="toggle(g.key)"
678
680
  >
@@ -710,7 +712,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
710
712
  {{ t('testing.concernCount', { count: g.concerns.length }, g.concerns.length) }}
711
713
  </template>
712
714
  </span>
713
- </button>
715
+ </UButton>
714
716
 
715
717
  <div v-if="!collapsed.has(g.key)" class="space-y-1 px-3 pb-3 ps-9">
716
718
  <!-- Outcomes -->
@@ -768,11 +770,13 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
768
770
 
769
771
  <!-- Screenshots captured for this scenario -->
770
772
  <div v-if="g.screenshots.length" class="mt-2 flex flex-wrap gap-2">
771
- <button
773
+ <UButton
774
+ color="neutral"
775
+ variant="ghost"
772
776
  v-for="(s, si) in g.screenshots"
773
777
  :key="`shot${si}`"
774
778
  data-testid="tester-screenshot"
775
- class="group relative h-20 w-28 shrink-0 overflow-hidden rounded-sm border border-default bg-app-950/60 hover:border-app-600"
779
+ class="group relative block h-20 w-28 shrink-0 overflow-hidden rounded-sm 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 hover:border-app-600"
776
780
  :title="s.view"
777
781
  @click="openShot(s.artifactId)"
778
782
  >
@@ -796,7 +800,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
796
800
  class="absolute inset-x-0 bottom-0 truncate bg-app-950/80 px-1 py-0.5 text-3xs text-toned"
797
801
  >{{ s.view }}</span
798
802
  >
799
- </button>
803
+ </UButton>
800
804
  </div>
801
805
  </div>
802
806
  </li>
@@ -808,10 +812,12 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
808
812
  {{ t('testing.screenshots') }}
809
813
  </SectionLabel>
810
814
  <div class="grid grid-cols-2 gap-3 sm:grid-cols-3">
811
- <button
815
+ <UButton
816
+ color="neutral"
817
+ variant="ghost"
812
818
  v-for="(s, si) in ungroupedScreenshots"
813
819
  :key="`gal${si}`"
814
- class="group relative aspect-video overflow-hidden rounded-lg border border-default bg-app-950/60 hover:border-app-600"
820
+ class="group relative block aspect-video overflow-hidden rounded-lg 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 hover:border-app-600"
815
821
  :title="s.view"
816
822
  @click="openShot(s.artifactId)"
817
823
  >
@@ -835,7 +841,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
835
841
  class="absolute inset-x-0 bottom-0 truncate bg-app-950/80 px-1.5 py-0.5 text-3xs text-toned"
836
842
  >{{ s.view }}</span
837
843
  >
838
- </button>
844
+ </UButton>
839
845
  </div>
840
846
  </section>
841
847
  </template>
@@ -66,7 +66,7 @@ async function submit() {
66
66
  :description="t('appearance.import.description')"
67
67
  >
68
68
  <template #body>
69
- <form class="space-y-4" data-testid="theme-import-form" @submit.prevent="submit">
69
+ <UForm class="space-y-4" data-testid="theme-import-form" @submit="submit">
70
70
  <UFormField
71
71
  :label="t('appearance.import.linkLabel')"
72
72
  :error="error ? t(ERROR_KEYS[error]) : undefined"
@@ -109,7 +109,7 @@ async function submit() {
109
109
  {{ t('appearance.import.submit') }}
110
110
  </UButton>
111
111
  </div>
112
- </form>
112
+ </UForm>
113
113
  </template>
114
114
  </UModal>
115
115
  </template>
@@ -204,21 +204,23 @@ async function uploadFor(view: string, file: File) {
204
204
  await visualConfirm.uploadReference(blockId.value, file, view)
205
205
  }
206
206
  const uploadView = ref('')
207
- const fileInput = ref<HTMLInputElement | null>(null)
208
- async function onFilePicked(e: Event) {
209
- const input = e.target as HTMLInputElement
210
- const file = input.files?.[0]
207
+ // The views this run already captured, offered as suggestions on the combobox.
208
+ const viewSuggestions = computed(() => pairs.value.map((p) => p.view))
209
+ const pendingUpload = ref<File | null>(null)
210
+ watch(pendingUpload, async (file) => {
211
211
  const view = uploadView.value.trim()
212
212
  // Require a view name: a reference with no view can't pair with any captured screenshot,
213
- // so it would be silently orphaned. The input is also disabled until a view is entered.
213
+ // so it would be silently orphaned. The control is also disabled until a view is entered.
214
214
  if (!file || !blockId.value || !view) {
215
- if (fileInput.value) fileInput.value.value = ''
215
+ pendingUpload.value = null
216
216
  return
217
217
  }
218
218
  await visualConfirm.uploadReference(blockId.value, file, view)
219
219
  uploadView.value = ''
220
- if (fileInput.value) fileInput.value.value = ''
221
- }
220
+ // Cleared so a fresh pick is a fresh file. `UFileUpload` carries `reset` for the other half of
221
+ // this: without it the native input keeps its value and the SAME file fires no change event.
222
+ pendingUpload.value = null
223
+ })
222
224
  </script>
223
225
 
224
226
  <template>
@@ -301,8 +303,10 @@ async function onFilePicked(e: Event) {
301
303
  />
302
304
  <!-- Per-view note (folded into the fixer findings) -->
303
305
  <div v-if="awaitingHuman" class="px-1">
304
- <button
305
- class="flex items-center gap-1.5 text-2xs text-muted hover:text-default"
306
+ <UButton
307
+ color="neutral"
308
+ variant="ghost"
309
+ class="flex items-center gap-1.5 p-0 text-2xs text-muted hover:bg-transparent hover:text-default"
306
310
  @click="noteOpen[p.view] = !noteOpen[p.view]"
307
311
  >
308
312
  <UIcon
@@ -315,13 +319,14 @@ async function onFilePicked(e: Event) {
315
319
  class="rounded-full bg-app-warning-500/15 px-1.5 text-3xs text-app-warning-300"
316
320
  >{{ t('visualConfirm.noted') }}</span
317
321
  >
318
- </button>
319
- <textarea
322
+ </UButton>
323
+ <UTextarea
320
324
  v-if="noteOpen[p.view]"
321
325
  v-model="perViewNotes[p.view]"
322
- rows="2"
326
+ :rows="2"
323
327
  :placeholder="t('visualConfirm.notePlaceholder', { view: p.view })"
324
- class="mt-1 w-full rounded-md border border-muted bg-app-950 px-2 py-1.5 text-xs text-default placeholder:text-app-600 focus:border-app-warning-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-app-warning-500/60"
328
+ size="xs"
329
+ class="mt-1 w-full"
325
330
  />
326
331
  </div>
327
332
  </div>
@@ -336,23 +341,44 @@ async function onFilePicked(e: Event) {
336
341
  {{ t('visualConfirm.upload.heading') }}
337
342
  </SectionLabel>
338
343
  <div class="flex flex-wrap items-center gap-2">
339
- <input
344
+ <!-- The known views are suggestions, not a closed list: a reference can be uploaded
345
+ for a view the run has not produced yet, which is the case this field exists for.
346
+ `mode="autocomplete"` is what makes that work: the DEFAULT combobox mode writes
347
+ its model only when something is SELECTED, so typing a new view name would leave
348
+ `uploadView` empty and the picker beside it disabled. -->
349
+ <UInputMenu
340
350
  v-model="uploadView"
341
- list="vc-views"
351
+ mode="autocomplete"
352
+ :items="viewSuggestions"
353
+ size="xs"
342
354
  :placeholder="t('visualConfirm.upload.viewPlaceholder')"
343
- class="rounded-md border border-muted bg-app-950 px-2 py-1 text-xs text-default placeholder:text-app-600"
344
355
  />
345
- <datalist id="vc-views">
346
- <option v-for="p in pairs" :key="p.view" :value="p.view" />
347
- </datalist>
348
- <input
349
- ref="fileInput"
350
- type="file"
356
+ <!-- `reset` so the native input is cleared on every open: re-picking the SAME file
357
+ after a rejected or completed upload otherwise fires no change event at all. -->
358
+ <!-- The trigger is our OWN button in the default slot, not `variant="button"`:
359
+ that variant renders the icon alone and drops `label` on the floor, so the
360
+ control reached a screen reader with no name at all. -->
361
+ <UFileUpload
362
+ v-model="pendingUpload"
363
+ size="xs"
364
+ reset
351
365
  accept="image/png,image/jpeg"
366
+ :preview="false"
352
367
  :disabled="busy || !uploadView.trim()"
353
- class="text-xs text-toned file:me-2 file:rounded-sm file:border-0 file:bg-elevated file:px-2 file:py-1 file:text-default disabled:opacity-40"
354
- @change="onFilePicked"
355
- />
368
+ class="w-fit"
369
+ >
370
+ <template #default="{ open }">
371
+ <UButton
372
+ color="neutral"
373
+ variant="soft"
374
+ size="xs"
375
+ icon="i-lucide-upload"
376
+ :disabled="busy || !uploadView.trim()"
377
+ :label="t('visualConfirm.upload.choose')"
378
+ @click="open()"
379
+ />
380
+ </template>
381
+ </UFileUpload>
356
382
  </div>
357
383
  <p class="mt-1.5 text-3xs text-app-600">
358
384
  {{
@@ -368,11 +394,12 @@ async function onFilePicked(e: Event) {
368
394
  <SectionLabel as="h3" class="mb-2">
369
395
  {{ t('visualConfirm.requestFix.heading') }}
370
396
  </SectionLabel>
371
- <textarea
397
+ <UTextarea
372
398
  v-model="globalFindings"
373
- rows="3"
399
+ :rows="3"
374
400
  :placeholder="t('visualConfirm.requestFix.placeholder')"
375
- class="w-full rounded-md border border-muted bg-app-950 px-3 py-2 text-sm text-default placeholder:text-app-600 focus:border-app-warning-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-app-warning-500/60"
401
+ size="sm"
402
+ class="w-full"
376
403
  />
377
404
  <div class="mt-2 flex items-center justify-between">
378
405
  <span class="text-2xs text-dimmed">
@@ -443,13 +470,13 @@ async function onFilePicked(e: Event) {
443
470
  :failure-at="instance?.failure?.occurredAt"
444
471
  />
445
472
  <div class="flex items-center gap-2">
446
- <label
473
+ <UCheckbox
447
474
  v-if="awaitingHuman && needsAck"
448
- class="flex items-center gap-1.5 text-2xs text-app-warning-300/90"
449
- >
450
- <input v-model="ackDegraded" type="checkbox" class="accent-app-warning-500" />
451
- {{ t('visualConfirm.reviewedManually') }}
452
- </label>
475
+ v-model="ackDegraded"
476
+ size="xs"
477
+ color="warning"
478
+ :label="t('visualConfirm.reviewedManually')"
479
+ />
453
480
  <UButton
454
481
  size="sm"
455
482
  variant="soft"
@@ -3455,7 +3455,8 @@
3455
3455
  "pastedLink": {
3456
3456
  "offer": "Diese Beschreibung verlinkt eine {source}-Seite. Als Kontext anhängen?",
3457
3457
  "attach": "Anhängen"
3458
- }
3458
+ },
3459
+ "removePending": "{name} entfernen"
3459
3460
  },
3460
3461
  "providers": {
3461
3462
  "presetMismatch": {
@@ -4939,7 +4940,8 @@
4939
4940
  "releaseInteractive": "Nicht mehr Standard in der App",
4940
4941
  "claimUnattended": "Als Standard ohne Aufsicht festlegen",
4941
4942
  "releaseUnattended": "Nicht mehr Standard ohne Aufsicht"
4942
- }
4943
+ },
4944
+ "removeLabel": "Label {label} entfernen"
4943
4945
  },
4944
4946
  "progress": {
4945
4947
  "status": {
@@ -7122,7 +7124,8 @@
7122
7124
  "heading": "Ein Referenzdesign hochladen",
7123
7125
  "viewPlaceholder": "Ansichtsname (z. B. login)",
7124
7126
  "tipReady": "Tipp: Ziehe ein Bild direkt auf ein Paar oben, um dessen Referenz festzulegen.",
7125
- "tipNeedView": "Gib zuerst einen Ansichtsnamen ein und wähle dann eine Datei. Oder ziehe ein Bild direkt auf ein Paar oben."
7127
+ "tipNeedView": "Gib zuerst einen Ansichtsnamen ein und wähle dann eine Datei. Oder ziehe ein Bild direkt auf ein Paar oben.",
7128
+ "choose": "Datei auswählen"
7126
7129
  },
7127
7130
  "requestFix": {
7128
7131
  "heading": "Eine Korrektur anfordern",
@@ -8522,6 +8525,7 @@
8522
8525
  "title": "Erzeugte Kandidaten",
8523
8526
  "titleWithBlock": "Erzeugte Kandidaten: {title}",
8524
8527
  "subtitle": "Vergleiche, was jede Integration erzeugt hat, und behalte, was du willst",
8528
+ "pickOne": "Wähle einen aus. Die Auswahl eines Kandidaten hebt die vorherige auf.",
8525
8529
  "automatic": "Es wurde nur ein Kandidat erzeugt, daher wurde er automatisch behalten. Niemand hat ihn geprüft.",
8526
8530
  "unlabelledSubject": "Kein Motiv angegeben",
8527
8531
  "noPreview": "Keine Vorschau verfügbar",
@@ -671,7 +671,8 @@
671
671
  "pastedLink": {
672
672
  "offer": "This description links a {source} page. Attach it as context?",
673
673
  "attach": "Attach"
674
- }
674
+ },
675
+ "removePending": "Remove {name}"
675
676
  },
676
677
  "errors": {
677
678
  "generic": {
@@ -5671,7 +5672,8 @@
5671
5672
  "releaseInteractive": "Stop being the in-app default",
5672
5673
  "claimUnattended": "Make the unattended default",
5673
5674
  "releaseUnattended": "Stop being the unattended default"
5674
- }
5675
+ },
5676
+ "removeLabel": "Remove the {label} label"
5675
5677
  },
5676
5678
  "progress": {
5677
5679
  "status": {
@@ -6723,7 +6725,8 @@
6723
6725
  "heading": "Upload a reference design",
6724
6726
  "viewPlaceholder": "View name (e.g. login)",
6725
6727
  "tipReady": "Tip: drop an image straight onto a pair above to set its reference.",
6726
- "tipNeedView": "Enter a view name first, then choose a file. Or drop an image straight onto a pair above."
6728
+ "tipNeedView": "Enter a view name first, then choose a file. Or drop an image straight onto a pair above.",
6729
+ "choose": "Choose a file"
6727
6730
  },
6728
6731
  "requestFix": {
6729
6732
  "heading": "Request a fix",
@@ -8844,6 +8847,7 @@
8844
8847
  "title": "Generated candidates",
8845
8848
  "titleWithBlock": "Generated candidates: {title}",
8846
8849
  "subtitle": "Compare what each integration produced, and keep what you want",
8850
+ "pickOne": "Pick one. Choosing a candidate clears the one picked before.",
8847
8851
  "automatic": "Only one candidate was generated, so it was kept automatically. Nobody reviewed it.",
8848
8852
  "unlabelledSubject": "No subject declared",
8849
8853
  "noPreview": "No preview available",
@@ -608,7 +608,8 @@
608
608
  "pastedLink": {
609
609
  "offer": "Esta descripción enlaza una página de {source}. ¿Adjuntarla como contexto?",
610
610
  "attach": "Adjuntar"
611
- }
611
+ },
612
+ "removePending": "Quitar {name}"
612
613
  },
613
614
  "errors": {
614
615
  "generic": {
@@ -5468,7 +5469,8 @@
5468
5469
  "releaseInteractive": "Dejar de ser la predeterminada en la app",
5469
5470
  "claimUnattended": "Hacer predeterminada sin supervisión",
5470
5471
  "releaseUnattended": "Dejar de ser la predeterminada sin supervisión"
5471
- }
5472
+ },
5473
+ "removeLabel": "Quitar la etiqueta {label}"
5472
5474
  },
5473
5475
  "progress": {
5474
5476
  "status": {
@@ -6392,7 +6394,8 @@
6392
6394
  "heading": "Subir un diseño de referencia",
6393
6395
  "viewPlaceholder": "Nombre de la vista (p. ej. login)",
6394
6396
  "tipReady": "Consejo: arrastra una imagen directamente sobre un par de arriba para establecer su referencia.",
6395
- "tipNeedView": "Introduce primero un nombre de vista y luego elige un archivo. O arrastra una imagen directamente sobre un par de arriba."
6397
+ "tipNeedView": "Introduce primero un nombre de vista y luego elige un archivo. O arrastra una imagen directamente sobre un par de arriba.",
6398
+ "choose": "Elegir un archivo"
6396
6399
  },
6397
6400
  "requestFix": {
6398
6401
  "heading": "Solicitar una corrección",
@@ -8522,6 +8525,7 @@
8522
8525
  "title": "Candidatos generados",
8523
8526
  "titleWithBlock": "Candidatos generados: {title}",
8524
8527
  "subtitle": "Compara lo que produjo cada integración y conserva lo que quieras",
8528
+ "pickOne": "Elige uno. Al elegir un candidato se quita el que elegiste antes.",
8525
8529
  "automatic": "Solo se generó un candidato, así que se conservó automáticamente. Nadie lo revisó.",
8526
8530
  "unlabelledSubject": "Sin sujeto declarado",
8527
8531
  "noPreview": "Sin vista previa disponible",
@@ -608,7 +608,8 @@
608
608
  "pastedLink": {
609
609
  "offer": "Cette description renvoie à une page {source}. L’attacher comme contexte ?",
610
610
  "attach": "Attacher"
611
- }
611
+ },
612
+ "removePending": "Retirer {name}"
612
613
  },
613
614
  "errors": {
614
615
  "generic": {
@@ -5468,7 +5469,8 @@
5468
5469
  "releaseInteractive": "Ne plus être le défaut dans l'application",
5469
5470
  "claimUnattended": "Définir comme défaut sans surveillance",
5470
5471
  "releaseUnattended": "Ne plus être le défaut sans surveillance"
5471
- }
5472
+ },
5473
+ "removeLabel": "Retirer le libellé {label}"
5472
5474
  },
5473
5475
  "progress": {
5474
5476
  "status": {
@@ -6392,7 +6394,8 @@
6392
6394
  "heading": "Importer une maquette de référence",
6393
6395
  "viewPlaceholder": "Nom de la vue (ex. login)",
6394
6396
  "tipReady": "Astuce : déposez une image directement sur une paire ci-dessus pour définir sa référence.",
6395
- "tipNeedView": "Saisissez d'abord un nom de vue, puis choisissez un fichier. Ou déposez une image directement sur une paire ci-dessus."
6397
+ "tipNeedView": "Saisissez d'abord un nom de vue, puis choisissez un fichier. Ou déposez une image directement sur une paire ci-dessus.",
6398
+ "choose": "Choisir un fichier"
6396
6399
  },
6397
6400
  "requestFix": {
6398
6401
  "heading": "Demander une correction",
@@ -8522,6 +8525,7 @@
8522
8525
  "title": "Candidats générés",
8523
8526
  "titleWithBlock": "Candidats générés : {title}",
8524
8527
  "subtitle": "Comparez ce que chaque intégration a produit et conservez ce que vous voulez",
8528
+ "pickOne": "Choisissez-en un. Choisir un candidat retire le choix précédent.",
8525
8529
  "automatic": "Un seul candidat a été généré, il a donc été conservé automatiquement. Personne ne l’a examiné.",
8526
8530
  "unlabelledSubject": "Aucun sujet déclaré",
8527
8531
  "noPreview": "Aucun aperçu disponible",
@@ -608,7 +608,8 @@
608
608
  "pastedLink": {
609
609
  "offer": "התיאור הזה מקשר לדף {source}. לצרף אותו כהקשר?",
610
610
  "attach": "צרף"
611
- }
611
+ },
612
+ "removePending": "הסר את {name}"
612
613
  },
613
614
  "errors": {
614
615
  "generic": {
@@ -5468,7 +5469,8 @@
5468
5469
  "releaseInteractive": "בטל כברירת מחדל באפליקציה",
5469
5470
  "claimUnattended": "הפוך לברירת המחדל ללא השגחה",
5470
5471
  "releaseUnattended": "בטל כברירת מחדל ללא השגחה"
5471
- }
5472
+ },
5473
+ "removeLabel": "הסר את התווית {label}"
5472
5474
  },
5473
5475
  "progress": {
5474
5476
  "status": {
@@ -6392,7 +6394,8 @@
6392
6394
  "heading": "העלה עיצוב ייחוס",
6393
6395
  "viewPlaceholder": "שם מסך (למשל login)",
6394
6396
  "tipReady": "טיפ: גרור תמונה ישירות על זוג למעלה כדי להגדיר את הייחוס שלו.",
6395
- "tipNeedView": "הזן תחילה שם מסך, ואז בחר קובץ. או גרור תמונה ישירות על זוג למעלה."
6397
+ "tipNeedView": "הזן תחילה שם מסך, ואז בחר קובץ. או גרור תמונה ישירות על זוג למעלה.",
6398
+ "choose": "בחירת קובץ"
6396
6399
  },
6397
6400
  "requestFix": {
6398
6401
  "heading": "בקש תיקון",
@@ -8522,6 +8525,7 @@
8522
8525
  "title": "מועמדים שנוצרו",
8523
8526
  "titleWithBlock": "מועמדים שנוצרו: {title}",
8524
8527
  "subtitle": "השוו מה כל אינטגרציה יצרה ושמרו את מה שאתם רוצים",
8528
+ "pickOne": "בחרו אחד. בחירת מועמד מבטלת את הבחירה הקודמת.",
8525
8529
  "automatic": "נוצר מועמד אחד בלבד, ולכן הוא נשמר אוטומטית. אף אחד לא בדק אותו.",
8526
8530
  "unlabelledSubject": "לא הוצהר נושא",
8527
8531
  "noPreview": "אין תצוגה מקדימה",
@@ -3455,7 +3455,8 @@
3455
3455
  "pastedLink": {
3456
3456
  "offer": "Questa descrizione rimanda a una pagina {source}. Allegarla come contesto?",
3457
3457
  "attach": "Allega"
3458
- }
3458
+ },
3459
+ "removePending": "Rimuovi {name}"
3459
3460
  },
3460
3461
  "providers": {
3461
3462
  "presetMismatch": {
@@ -4939,7 +4940,8 @@
4939
4940
  "releaseInteractive": "Non più predefinita nell'app",
4940
4941
  "claimUnattended": "Rendi predefinita non presidiata",
4941
4942
  "releaseUnattended": "Non più predefinita non presidiata"
4942
- }
4943
+ },
4944
+ "removeLabel": "Rimuovi l'etichetta {label}"
4943
4945
  },
4944
4946
  "progress": {
4945
4947
  "status": {
@@ -7122,7 +7124,8 @@
7122
7124
  "heading": "Carica un design di riferimento",
7123
7125
  "viewPlaceholder": "Nome della vista (es. login)",
7124
7126
  "tipReady": "Suggerimento: trascina un'immagine direttamente su una coppia sopra per impostarne il riferimento.",
7125
- "tipNeedView": "Inserisci prima un nome per la vista, poi scegli un file. Oppure trascina un'immagine direttamente su una coppia sopra."
7127
+ "tipNeedView": "Inserisci prima un nome per la vista, poi scegli un file. Oppure trascina un'immagine direttamente su una coppia sopra.",
7128
+ "choose": "Scegli un file"
7126
7129
  },
7127
7130
  "requestFix": {
7128
7131
  "heading": "Richiedi una correzione",
@@ -8522,6 +8525,7 @@
8522
8525
  "title": "Candidati generati",
8523
8526
  "titleWithBlock": "Candidati generati: {title}",
8524
8527
  "subtitle": "Confronta ciò che ogni integrazione ha prodotto e tieni quello che vuoi",
8528
+ "pickOne": "Scegline uno. Scegliere un candidato annulla la scelta precedente.",
8525
8529
  "automatic": "È stato generato un solo candidato, quindi è stato tenuto automaticamente. Nessuno lo ha esaminato.",
8526
8530
  "unlabelledSubject": "Nessun soggetto dichiarato",
8527
8531
  "noPreview": "Nessuna anteprima disponibile",
@@ -608,7 +608,8 @@
608
608
  "pastedLink": {
609
609
  "offer": "この説明は {source} のページにリンクしています。コンテキストとして添付しますか?",
610
610
  "attach": "添付"
611
- }
611
+ },
612
+ "removePending": "{name} を削除"
612
613
  },
613
614
  "errors": {
614
615
  "generic": {
@@ -5468,7 +5469,8 @@
5468
5469
  "releaseInteractive": "アプリ内の既定を解除",
5469
5470
  "claimUnattended": "無人実行の既定にする",
5470
5471
  "releaseUnattended": "無人実行の既定を解除"
5471
- }
5472
+ },
5473
+ "removeLabel": "ラベル {label} を削除"
5472
5474
  },
5473
5475
  "progress": {
5474
5476
  "status": {
@@ -6392,7 +6394,8 @@
6392
6394
  "heading": "リファレンスデザインをアップロード",
6393
6395
  "viewPlaceholder": "ビュー名(例: login)",
6394
6396
  "tipReady": "ヒント: 上のペアに画像を直接ドロップするとリファレンスに設定できます。",
6395
- "tipNeedView": "まずビュー名を入力してからファイルを選択してください。または上のペアに画像を直接ドロップしてください。"
6397
+ "tipNeedView": "まずビュー名を入力してからファイルを選択してください。または上のペアに画像を直接ドロップしてください。",
6398
+ "choose": "ファイルを選択"
6396
6399
  },
6397
6400
  "requestFix": {
6398
6401
  "heading": "修正をリクエスト",
@@ -8522,6 +8525,7 @@
8522
8525
  "title": "生成された候補",
8523
8526
  "titleWithBlock": "生成された候補: {title}",
8524
8527
  "subtitle": "各連携が生成したものを比較し、残すものを選びます",
8528
+ "pickOne": "1つ選びます。候補を選ぶと、前に選んだものは解除されます。",
8525
8529
  "automatic": "候補が1つしか生成されなかったため、自動的に採用されました。誰も確認していません。",
8526
8530
  "unlabelledSubject": "対象の申告なし",
8527
8531
  "noPreview": "プレビューはありません",