@cat-factory/app 0.96.2 → 0.96.4
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.
- package/app/assets/css/main.css +15 -0
- package/app/components/auth/LoginScreen.vue +3 -4
- package/app/components/auth/ResetPasswordScreen.vue +3 -4
- package/app/components/board/nodes/TaskPipelineMini.vue +4 -3
- package/app/components/brainstorm/BrainstormWindow.vue +9 -1
- package/app/components/clarity/ClarityReviewWindow.vue +9 -1
- package/app/components/common/IconButton.vue +19 -0
- package/app/components/common/SecretInput.vue +49 -0
- package/app/components/documents/DocumentSourceConnectModal.vue +3 -2
- package/app/components/focus/BlockFocusView.vue +8 -1
- package/app/components/followUp/FollowUpWindow.vue +1 -1
- package/app/components/gates/GateResultView.vue +1 -1
- package/app/components/humanTest/HumanTestWindow.vue +1 -1
- package/app/components/layout/AccountDeploymentSettings.vue +7 -12
- package/app/components/layout/AccountTeamSettings.vue +2 -2
- package/app/components/panels/InspectorPanel.vue +3 -1
- package/app/components/pipeline/PipelineProgress.vue +8 -0
- package/app/components/providers/ApiKeysSection.vue +3 -3
- package/app/components/providers/PersonalCredentialModal.vue +2 -2
- package/app/components/providers/PersonalSubscriptionSection.vue +4 -5
- package/app/components/providers/VendorCredentialsModal.vue +3 -3
- package/app/components/requirements/RequirementsReviewWindow.vue +9 -1
- package/app/components/settings/KubernetesEngineForm.vue +3 -3
- package/app/components/settings/KubernetesEnvironmentForm.vue +2 -1
- package/app/components/settings/LocalModelEndpointsPanel.vue +2 -2
- package/app/components/settings/ObservabilityConnectionPanel.vue +5 -10
- package/app/components/settings/OpenRouterCatalogPanel.vue +3 -3
- package/app/components/settings/PackageRegistriesPanel.vue +2 -6
- package/app/components/settings/ProviderManifestEditor.vue +2 -2
- package/app/components/settings/UserSecretsSection.vue +3 -2
- package/app/components/slack/SlackPanel.vue +2 -7
- package/app/components/spec/ServiceSpecWindow.vue +9 -1
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +2 -2
- package/i18n/locales/en.json +2 -0
- package/i18n/locales/es.json +2 -0
- package/i18n/locales/fr.json +2 -0
- package/i18n/locales/he.json +2 -0
- package/i18n/locales/ja.json +2 -0
- package/i18n/locales/pl.json +2 -0
- package/i18n/locales/tr.json +2 -0
- package/i18n/locales/uk.json +2 -0
- package/package.json +2 -2
package/app/assets/css/main.css
CHANGED
|
@@ -94,6 +94,21 @@ body {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/* Respect prefers-reduced-motion: the decorative, infinite attention pulses (the
|
|
98
|
+
* blocked / decision-needed halo, the PR-ready halo) and the marching-ants edge
|
|
99
|
+
* animation convey nothing the static state doesn't already, so we disable them for
|
|
100
|
+
* users who ask for less motion. Loading spinners are deliberately left alone — a
|
|
101
|
+
* spinner's motion IS its meaning ("work in progress"). */
|
|
102
|
+
@media (prefers-reduced-motion: reduce) {
|
|
103
|
+
.board-pulse,
|
|
104
|
+
.board-pulse-green {
|
|
105
|
+
animation: none;
|
|
106
|
+
}
|
|
107
|
+
.vue-flow__edge.is-live .vue-flow__edge-path {
|
|
108
|
+
animation: none;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
/* Custom block node should not show the default node chrome */
|
|
98
113
|
.vue-flow__node-block {
|
|
99
114
|
padding: 0;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, ref, watch } from 'vue'
|
|
3
3
|
import { apiErrorEnvelope } from '~/composables/api/errors'
|
|
4
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
4
5
|
|
|
5
6
|
const auth = useAuthStore()
|
|
6
7
|
const { t } = useI18n()
|
|
@@ -320,9 +321,8 @@ const noSignInMethod = computed(
|
|
|
320
321
|
size="lg"
|
|
321
322
|
class="w-full"
|
|
322
323
|
/>
|
|
323
|
-
<
|
|
324
|
+
<SecretInput
|
|
324
325
|
v-model="password"
|
|
325
|
-
type="password"
|
|
326
326
|
required
|
|
327
327
|
:placeholder="t('auth.login.passwordPlaceholder')"
|
|
328
328
|
icon="i-lucide-lock"
|
|
@@ -395,9 +395,8 @@ const noSignInMethod = computed(
|
|
|
395
395
|
{{ PROVIDER_LABELS[p] }}
|
|
396
396
|
</UButton>
|
|
397
397
|
</div>
|
|
398
|
-
<
|
|
398
|
+
<SecretInput
|
|
399
399
|
v-model="remotePatToken"
|
|
400
|
-
type="password"
|
|
401
400
|
required
|
|
402
401
|
:placeholder="
|
|
403
402
|
t('auth.login.patPlaceholder', { provider: PROVIDER_LABELS[remotePatProvider] })
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
3
4
|
|
|
4
5
|
// Standalone full-screen reset form reached from the emailed link
|
|
5
6
|
// (`/reset-password?token=…`). It is a public route (see AuthGate), so a recipient who
|
|
@@ -76,18 +77,16 @@ function goToLogin() {
|
|
|
76
77
|
</template>
|
|
77
78
|
|
|
78
79
|
<form v-else class="space-y-3" @submit.prevent="submit">
|
|
79
|
-
<
|
|
80
|
+
<SecretInput
|
|
80
81
|
v-model="password"
|
|
81
|
-
type="password"
|
|
82
82
|
required
|
|
83
83
|
:placeholder="t('auth.resetPassword.newPasswordPlaceholder')"
|
|
84
84
|
icon="i-lucide-lock"
|
|
85
85
|
size="lg"
|
|
86
86
|
class="w-full"
|
|
87
87
|
/>
|
|
88
|
-
<
|
|
88
|
+
<SecretInput
|
|
89
89
|
v-model="confirm"
|
|
90
|
-
type="password"
|
|
91
90
|
required
|
|
92
91
|
:placeholder="t('auth.resetPassword.confirmPasswordPlaceholder')"
|
|
93
92
|
icon="i-lucide-lock"
|
|
@@ -79,8 +79,9 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
79
79
|
{{ t('board.task.buildSteps') }}
|
|
80
80
|
</div>
|
|
81
81
|
<div v-for="(s, i) in steps" :key="i" class="rounded bg-slate-900/60 px-1.5 py-1">
|
|
82
|
-
<
|
|
83
|
-
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
class="flex w-full cursor-pointer items-center gap-1 rounded text-start focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500/60"
|
|
84
85
|
:title="`${agentKindMeta(s.agentKind).label} — ${agentKindMeta(s.agentKind).description}\n${t('board.task.clickToViewStep')}`"
|
|
85
86
|
@click.stop="openStep(i)"
|
|
86
87
|
>
|
|
@@ -116,7 +117,7 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
116
117
|
: STATE_META[s.state].color,
|
|
117
118
|
}"
|
|
118
119
|
/>
|
|
119
|
-
</
|
|
120
|
+
</button>
|
|
120
121
|
|
|
121
122
|
<!-- pending approval gate: jump straight to the conclusions reader. Suppressed
|
|
122
123
|
while a reviewer gate is folding/re-reviewing in the background (no human needed). -->
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// options or hits the iteration cap. The converged direction — not the original description — is
|
|
9
9
|
// what the downstream stage (the requirements review / the architect) consumes.
|
|
10
10
|
import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
|
|
11
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
11
12
|
import { parseOutputOutline } from '~/utils/agentOutput'
|
|
12
13
|
import type {
|
|
13
14
|
BrainstormItem,
|
|
@@ -281,7 +282,14 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
281
282
|
<UBadge v-if="session" color="neutral" variant="subtle" size="sm">
|
|
282
283
|
{{ t('brainstorm.iteration', { current: iteration, max: maxIterations }) }}
|
|
283
284
|
</UBadge>
|
|
284
|
-
<
|
|
285
|
+
<IconButton
|
|
286
|
+
icon="i-lucide-x"
|
|
287
|
+
color="neutral"
|
|
288
|
+
variant="ghost"
|
|
289
|
+
size="sm"
|
|
290
|
+
:label="t('common.close')"
|
|
291
|
+
@click="close"
|
|
292
|
+
/>
|
|
285
293
|
</div>
|
|
286
294
|
</header>
|
|
287
295
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// description — is what every downstream agent step (the bug investigator, the coder)
|
|
10
10
|
// consumes.
|
|
11
11
|
import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
|
|
12
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
12
13
|
import { parseOutputOutline } from '~/utils/agentOutput'
|
|
13
14
|
import type {
|
|
14
15
|
ClarityItemStatus,
|
|
@@ -327,7 +328,14 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
327
328
|
<UBadge v-if="review" color="neutral" variant="subtle" size="sm">
|
|
328
329
|
{{ t('clarity.iteration', { current: iteration, max: maxIterations }) }}
|
|
329
330
|
</UBadge>
|
|
330
|
-
<
|
|
331
|
+
<IconButton
|
|
332
|
+
icon="i-lucide-x"
|
|
333
|
+
color="neutral"
|
|
334
|
+
variant="ghost"
|
|
335
|
+
size="sm"
|
|
336
|
+
:label="t('common.close')"
|
|
337
|
+
@click="close"
|
|
338
|
+
/>
|
|
331
339
|
</div>
|
|
332
340
|
</header>
|
|
333
341
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Shared icon-only button primitive that ENFORCES the accessible-name convention (UX-62/63):
|
|
3
|
+
// every icon button must carry a `label`, applied as BOTH `title` (pointer tooltip) and
|
|
4
|
+
// `aria-label` (screen readers) — `title` alone never fires on touch and is invisible to
|
|
5
|
+
// assistive tech, so an unlabeled icon button is a bug this component makes unrepresentable.
|
|
6
|
+
// All other UButton props/listeners (icon, color, variant, size, @click, :loading, :disabled)
|
|
7
|
+
// pass straight through via `$attrs`; `label` is declared as a prop so it strips off before
|
|
8
|
+
// reaching UButton (whose own `label` prop would otherwise render visible text). Mirrors the
|
|
9
|
+
// shape of `common/CopyButton.vue`.
|
|
10
|
+
defineProps<{
|
|
11
|
+
/** Accessible name + tooltip. Required — the whole point of the primitive. */
|
|
12
|
+
label: string
|
|
13
|
+
}>()
|
|
14
|
+
defineOptions({ inheritAttrs: false })
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<UButton v-bind="$attrs" :title="label" :aria-label="label" />
|
|
19
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Shared single-line secret/password input with a reveal (eye) toggle (UX-19/UX-20).
|
|
3
|
+
// Masks the value by default (`type="password"`) and lets the user verify a pasted token via
|
|
4
|
+
// the trailing eye button — without one, a mistyped/expired key looks identical to a good one,
|
|
5
|
+
// the leading cause of invalid-credential retries. Replaces both the bare `type="password"`
|
|
6
|
+
// UInputs (UX-19) and the fully-plaintext secret `UTextarea`s (UX-20), which rendered live
|
|
7
|
+
// vendor keys in cleartext (shoulder-surf / screen-share leakage).
|
|
8
|
+
//
|
|
9
|
+
// When `secret` is false it degrades to a plain text input with no toggle, so descriptor-driven
|
|
10
|
+
// fields whose secrecy is data-dependent (`field.secret ? … : …`) can bind it directly.
|
|
11
|
+
// All other UInput props/listeners (icon, size, placeholder, disabled, autofocus, class, …)
|
|
12
|
+
// pass straight through via `$attrs`; `secret` is a declared prop so it strips off first.
|
|
13
|
+
// `$attrs` is bound BEFORE `type` so the mask/reveal control stays authoritative — a caller
|
|
14
|
+
// that (out of old habit) also passes `type="password"` can't clobber the toggle.
|
|
15
|
+
// Mirrors the shape of `common/CopyButton.vue` / `common/IconButton.vue`.
|
|
16
|
+
const props = withDefaults(
|
|
17
|
+
defineProps<{
|
|
18
|
+
/** Whether the field holds a secret (masked + reveal toggle). When false, a plain text input. */
|
|
19
|
+
secret?: boolean
|
|
20
|
+
}>(),
|
|
21
|
+
{ secret: true },
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const model = defineModel<string>()
|
|
25
|
+
const { t } = useI18n()
|
|
26
|
+
const revealed = ref(false)
|
|
27
|
+
const toggle = () => {
|
|
28
|
+
revealed.value = !revealed.value
|
|
29
|
+
}
|
|
30
|
+
defineOptions({ inheritAttrs: false })
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<UInput v-if="!props.secret" v-model="model" v-bind="$attrs" type="text" />
|
|
35
|
+
<UInput v-else v-model="model" v-bind="$attrs" :type="revealed ? 'text' : 'password'">
|
|
36
|
+
<template #trailing>
|
|
37
|
+
<UButton
|
|
38
|
+
color="neutral"
|
|
39
|
+
variant="link"
|
|
40
|
+
size="xs"
|
|
41
|
+
:icon="revealed ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
|
42
|
+
:title="revealed ? t('common.hide') : t('common.reveal')"
|
|
43
|
+
:aria-label="revealed ? t('common.hide') : t('common.reveal')"
|
|
44
|
+
:aria-pressed="revealed"
|
|
45
|
+
@click.stop="toggle"
|
|
46
|
+
/>
|
|
47
|
+
</template>
|
|
48
|
+
</UInput>
|
|
49
|
+
</template>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// are write-only — the backend never returns them, so on reload we show
|
|
6
6
|
// "Connected" with empty fields.
|
|
7
7
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
8
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
8
9
|
|
|
9
10
|
const { t } = useI18n()
|
|
10
11
|
const ui = useUiStore()
|
|
@@ -103,9 +104,9 @@ async function disconnect() {
|
|
|
103
104
|
:label="field.label"
|
|
104
105
|
:help="field.help"
|
|
105
106
|
>
|
|
106
|
-
<
|
|
107
|
+
<SecretInput
|
|
107
108
|
v-model="values[field.key]"
|
|
108
|
-
:
|
|
109
|
+
:secret="!!field.secret"
|
|
109
110
|
:placeholder="field.placeholder"
|
|
110
111
|
class="w-full"
|
|
111
112
|
/>
|
|
@@ -4,6 +4,7 @@ import type { Block } from '~/types/domain'
|
|
|
4
4
|
import { blockTypeMeta, STATUS_META } from '~/utils/catalog'
|
|
5
5
|
import { pipelineAllowedForManualStart } from '~/utils/pipeline'
|
|
6
6
|
import PipelineProgress from '~/components/pipeline/PipelineProgress.vue'
|
|
7
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
7
8
|
|
|
8
9
|
const board = useBoardStore()
|
|
9
10
|
const pipelines = usePipelinesStore()
|
|
@@ -106,7 +107,13 @@ function openApprovalFor(approvalId: string) {
|
|
|
106
107
|
{{ instance ? t('focus.rerunPipeline') : t('focus.runPipeline') }}
|
|
107
108
|
</UButton>
|
|
108
109
|
</UDropdownMenu>
|
|
109
|
-
<
|
|
110
|
+
<IconButton
|
|
111
|
+
icon="i-lucide-x"
|
|
112
|
+
color="neutral"
|
|
113
|
+
variant="ghost"
|
|
114
|
+
:label="t('common.close')"
|
|
115
|
+
@click="close"
|
|
116
|
+
/>
|
|
110
117
|
</div>
|
|
111
118
|
</header>
|
|
112
119
|
|
|
@@ -200,7 +200,7 @@ const STATUS_META: Record<
|
|
|
200
200
|
v-model="drafts[item.id]"
|
|
201
201
|
rows="2"
|
|
202
202
|
:placeholder="t('followUp.answerPlaceholder')"
|
|
203
|
-
class="w-full resize-y rounded-md border border-slate-700 bg-slate-950/60 px-2.5 py-1.5 text-[12px] text-slate-100 placeholder:text-slate-600 focus:border-sky-500 focus:outline-none"
|
|
203
|
+
class="w-full resize-y rounded-md border border-slate-700 bg-slate-950/60 px-2.5 py-1.5 text-[12px] text-slate-100 placeholder:text-slate-600 focus:border-sky-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/60"
|
|
204
204
|
/>
|
|
205
205
|
<div class="flex items-center gap-2">
|
|
206
206
|
<UButton
|
|
@@ -297,7 +297,7 @@ const conflictVerdict = computed(() => {
|
|
|
297
297
|
rows="3"
|
|
298
298
|
:disabled="fixBusy"
|
|
299
299
|
:placeholder="t('gates.humanReview.requestFixPlaceholder')"
|
|
300
|
-
class="w-full resize-y rounded-md border border-slate-800 bg-slate-950/60 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-violet-500/60 focus:outline-none"
|
|
300
|
+
class="w-full resize-y rounded-md border border-slate-800 bg-slate-950/60 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-violet-500/60 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-500/60"
|
|
301
301
|
/>
|
|
302
302
|
<div class="mt-2 flex justify-end">
|
|
303
303
|
<UButton
|
|
@@ -289,7 +289,7 @@ const canDestroy = computed(
|
|
|
289
289
|
v-model="findings"
|
|
290
290
|
rows="4"
|
|
291
291
|
:placeholder="t('humanTest.fix.placeholder')"
|
|
292
|
-
class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none"
|
|
292
|
+
class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/60"
|
|
293
293
|
/>
|
|
294
294
|
<UButton
|
|
295
295
|
size="sm"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
|
3
3
|
import type { ContentStorageBackend, ContentStorageConfig } from '~/types/accountSettings'
|
|
4
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
4
5
|
|
|
5
6
|
// Deployment integration secrets for an account (admin only): the Slack app OAuth
|
|
6
7
|
// credentials, the container web-search upstream keys, and the binary-artifact (screenshot)
|
|
@@ -379,9 +380,8 @@ async function clearWeb() {
|
|
|
379
380
|
:placeholder="t('layout.accountDeployment.slack.clientId')"
|
|
380
381
|
size="sm"
|
|
381
382
|
/>
|
|
382
|
-
<
|
|
383
|
+
<SecretInput
|
|
383
384
|
v-model="slack.clientSecret"
|
|
384
|
-
type="password"
|
|
385
385
|
:placeholder="t('layout.accountDeployment.slack.clientSecret')"
|
|
386
386
|
size="sm"
|
|
387
387
|
/>
|
|
@@ -441,9 +441,8 @@ async function clearWeb() {
|
|
|
441
441
|
:placeholder="t('layout.accountDeployment.linear.clientId')"
|
|
442
442
|
size="sm"
|
|
443
443
|
/>
|
|
444
|
-
<
|
|
444
|
+
<SecretInput
|
|
445
445
|
v-model="linear.clientSecret"
|
|
446
|
-
type="password"
|
|
447
446
|
:placeholder="t('layout.accountDeployment.linear.clientSecret')"
|
|
448
447
|
size="sm"
|
|
449
448
|
/>
|
|
@@ -494,9 +493,8 @@ async function clearWeb() {
|
|
|
494
493
|
{{ t('layout.accountDeployment.web.description') }}
|
|
495
494
|
</p>
|
|
496
495
|
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
|
497
|
-
<
|
|
496
|
+
<SecretInput
|
|
498
497
|
v-model="web.braveApiKey"
|
|
499
|
-
type="password"
|
|
500
498
|
:placeholder="t('layout.accountDeployment.web.braveKey')"
|
|
501
499
|
size="sm"
|
|
502
500
|
/>
|
|
@@ -505,9 +503,8 @@ async function clearWeb() {
|
|
|
505
503
|
:placeholder="t('layout.accountDeployment.web.searxngUrl')"
|
|
506
504
|
size="sm"
|
|
507
505
|
/>
|
|
508
|
-
<
|
|
506
|
+
<SecretInput
|
|
509
507
|
v-model="web.searxngApiKey"
|
|
510
|
-
type="password"
|
|
511
508
|
:placeholder="t('layout.accountDeployment.web.searxngKey')"
|
|
512
509
|
size="sm"
|
|
513
510
|
/>
|
|
@@ -624,15 +621,13 @@ async function clearWeb() {
|
|
|
624
621
|
</UBadge>
|
|
625
622
|
</div>
|
|
626
623
|
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
627
|
-
<
|
|
624
|
+
<SecretInput
|
|
628
625
|
v-model="cs.accessKeyId"
|
|
629
|
-
type="password"
|
|
630
626
|
:placeholder="t('layout.accountDeployment.contentStorage.accessKeyId')"
|
|
631
627
|
size="sm"
|
|
632
628
|
/>
|
|
633
|
-
<
|
|
629
|
+
<SecretInput
|
|
634
630
|
v-model="cs.secretAccessKey"
|
|
635
|
-
type="password"
|
|
636
631
|
:placeholder="t('layout.accountDeployment.contentStorage.secretAccessKey')"
|
|
637
632
|
size="sm"
|
|
638
633
|
/>
|
|
@@ -4,6 +4,7 @@ import { apiErrorEnvelope } from '~/composables/api/errors'
|
|
|
4
4
|
import type { AccountRole } from '~/types/domain'
|
|
5
5
|
import type { InvitationStatus } from '@cat-factory/contracts'
|
|
6
6
|
import AccountDeploymentSettings from '~/components/layout/AccountDeploymentSettings.vue'
|
|
7
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
7
8
|
|
|
8
9
|
// Team settings for an org account: the member roster (with combinable admin /
|
|
9
10
|
// developer / product roles), pending email invitations, and the per-account
|
|
@@ -300,9 +301,8 @@ async function disconnectEmail() {
|
|
|
300
301
|
:placeholder="t('layout.accountTeam.email.fromPlaceholder')"
|
|
301
302
|
class="w-full"
|
|
302
303
|
/>
|
|
303
|
-
<
|
|
304
|
+
<SecretInput
|
|
304
305
|
v-model="emailApiKey"
|
|
305
|
-
type="password"
|
|
306
306
|
:placeholder="t('layout.accountTeam.email.apiKeyPlaceholder')"
|
|
307
307
|
class="w-full"
|
|
308
308
|
/>
|
|
@@ -18,6 +18,7 @@ import TaskExecution from '~/components/panels/inspector/TaskExecution.vue'
|
|
|
18
18
|
import TaskEstimateBadge from '~/components/panels/inspector/TaskEstimateBadge.vue'
|
|
19
19
|
import EpicChildren from '~/components/panels/inspector/EpicChildren.vue'
|
|
20
20
|
import InitiativeInspector from '~/components/panels/inspector/InitiativeInspector.vue'
|
|
21
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
21
22
|
import RecurringScheduleSettings from '~/components/panels/inspector/RecurringScheduleSettings.vue'
|
|
22
23
|
import AgentFailureCard from '~/components/board/AgentFailureCard.vue'
|
|
23
24
|
import AgentStopButton from '~/components/board/AgentStopButton.vue'
|
|
@@ -265,11 +266,12 @@ const showOriginalDescription = ref(false)
|
|
|
265
266
|
</div>
|
|
266
267
|
</div>
|
|
267
268
|
</div>
|
|
268
|
-
<
|
|
269
|
+
<IconButton
|
|
269
270
|
icon="i-lucide-x"
|
|
270
271
|
color="neutral"
|
|
271
272
|
variant="ghost"
|
|
272
273
|
size="xs"
|
|
274
|
+
:label="t('common.close')"
|
|
273
275
|
@click="ui.select(null)"
|
|
274
276
|
/>
|
|
275
277
|
</div>
|
|
@@ -607,4 +607,12 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
607
607
|
.followup-blink {
|
|
608
608
|
animation: followup-blink 1.4s ease-in-out infinite;
|
|
609
609
|
}
|
|
610
|
+
/* These decorative attention halos carry no information the static state lacks, so
|
|
611
|
+
silence them under prefers-reduced-motion (matches the board-pulse rule in main.css). */
|
|
612
|
+
@media (prefers-reduced-motion: reduce) {
|
|
613
|
+
.step-active,
|
|
614
|
+
.followup-blink {
|
|
615
|
+
animation: none;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
610
618
|
</style>
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// account); admin-only, enforced server-side. Surfaced from account/team settings.
|
|
17
17
|
import { computed, ref, watch } from 'vue'
|
|
18
18
|
import type { ApiKey, ApiKeyProvider } from '~/types/domain'
|
|
19
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
19
20
|
|
|
20
21
|
const props = withDefaults(defineProps<{ accountId?: string; category?: 'direct' | 'proxy' }>(), {
|
|
21
22
|
category: 'direct',
|
|
@@ -323,12 +324,11 @@ async function remove(k: ApiKey) {
|
|
|
323
324
|
/>
|
|
324
325
|
</UFormField>
|
|
325
326
|
<UFormField :label="t('providers.apiKeys.keyField')">
|
|
326
|
-
<
|
|
327
|
+
<SecretInput
|
|
327
328
|
v-model="key"
|
|
328
|
-
:rows="2"
|
|
329
329
|
:disabled="needsSignIn"
|
|
330
330
|
:placeholder="t('providers.apiKeys.keyPlaceholder')"
|
|
331
|
-
class="font-mono"
|
|
331
|
+
class="w-full font-mono"
|
|
332
332
|
/>
|
|
333
333
|
</UFormField>
|
|
334
334
|
<div class="flex justify-end">
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// credential_required. On submit it transparently retries the gated action and caches the
|
|
6
6
|
// password. The copy follows the pending vendor (Claude / GLM / ChatGPT-Codex).
|
|
7
7
|
import { computed, ref, watch } from 'vue'
|
|
8
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
8
9
|
|
|
9
10
|
const { t } = useI18n()
|
|
10
11
|
const personal = usePersonalSubscriptionsStore()
|
|
@@ -119,9 +120,8 @@ function goConnect() {
|
|
|
119
120
|
{{ t('providers.personalCredential.passwordBody', { vendor: vendorLabel }) }}
|
|
120
121
|
</p>
|
|
121
122
|
<UFormField :label="t('providers.personalCredential.passwordField')">
|
|
122
|
-
<
|
|
123
|
+
<SecretInput
|
|
123
124
|
v-model="password"
|
|
124
|
-
type="password"
|
|
125
125
|
autofocus
|
|
126
126
|
:placeholder="t('providers.personalCredential.passwordPlaceholder')"
|
|
127
127
|
@keydown.enter="submit()"
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// such a run (cached locally so it's usually transparent). Recurring schedules can't use them.
|
|
7
7
|
import { computed, onMounted, ref } from 'vue'
|
|
8
8
|
import type { SubscriptionVendor } from '~/types/domain'
|
|
9
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
9
10
|
|
|
10
11
|
const personal = usePersonalSubscriptionsStore()
|
|
11
12
|
const auth = useAuthStore()
|
|
@@ -233,19 +234,17 @@ async function disconnect(v: SubscriptionVendor) {
|
|
|
233
234
|
/>
|
|
234
235
|
</UFormField>
|
|
235
236
|
<UFormField :label="selectedMeta.tokenLabel">
|
|
236
|
-
<
|
|
237
|
+
<SecretInput
|
|
237
238
|
v-model="token"
|
|
238
|
-
:rows="2"
|
|
239
239
|
:disabled="needsSignIn"
|
|
240
240
|
:placeholder="selectedMeta.tokenPlaceholder"
|
|
241
|
-
class="font-mono"
|
|
241
|
+
class="w-full font-mono"
|
|
242
242
|
/>
|
|
243
243
|
</UFormField>
|
|
244
244
|
<div class="flex flex-wrap gap-3">
|
|
245
245
|
<UFormField :label="t('personalSubscriptions.passwordField')" class="flex-1">
|
|
246
|
-
<
|
|
246
|
+
<SecretInput
|
|
247
247
|
v-model="password"
|
|
248
|
-
type="password"
|
|
249
248
|
:disabled="needsSignIn"
|
|
250
249
|
:placeholder="t('personalSubscriptions.passwordPlaceholder')"
|
|
251
250
|
/>
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { computed, ref, watch } from 'vue'
|
|
9
9
|
import type { SubscriptionVendor } from '~/types/domain'
|
|
10
10
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
11
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
11
12
|
|
|
12
13
|
const { t, n } = useI18n()
|
|
13
14
|
const ui = useUiStore()
|
|
@@ -215,11 +216,10 @@ function vendorLabel(v: SubscriptionVendor): string {
|
|
|
215
216
|
/>
|
|
216
217
|
</UFormField>
|
|
217
218
|
<UFormField :label="t('providers.vendorCredentials.tokenField')">
|
|
218
|
-
<
|
|
219
|
+
<SecretInput
|
|
219
220
|
v-model="token"
|
|
220
|
-
:rows="3"
|
|
221
221
|
:placeholder="tokenPlaceholder"
|
|
222
|
-
class="font-mono"
|
|
222
|
+
class="w-full font-mono"
|
|
223
223
|
/>
|
|
224
224
|
</UFormField>
|
|
225
225
|
<div class="flex justify-end">
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { parseOutputOutline } from '~/utils/agentOutput'
|
|
11
11
|
import StepRestartControl from '~/components/panels/StepRestartControl.vue'
|
|
12
12
|
import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
|
|
13
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
13
14
|
import type {
|
|
14
15
|
RequirementRecommendation,
|
|
15
16
|
RequirementReview,
|
|
@@ -489,7 +490,14 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
489
490
|
:step-index="stepIndex"
|
|
490
491
|
@restarted="close"
|
|
491
492
|
/>
|
|
492
|
-
<
|
|
493
|
+
<IconButton
|
|
494
|
+
icon="i-lucide-x"
|
|
495
|
+
color="neutral"
|
|
496
|
+
variant="ghost"
|
|
497
|
+
size="sm"
|
|
498
|
+
:label="t('common.close')"
|
|
499
|
+
@click="close"
|
|
500
|
+
/>
|
|
493
501
|
</div>
|
|
494
502
|
</header>
|
|
495
503
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// single-connection backend, which still carries the manifest source inline).
|
|
9
9
|
import { computed, reactive, ref, watch } from 'vue'
|
|
10
10
|
import { KUBERNETES_ENV_TOKEN_SECRET_KEY } from '@cat-factory/contracts'
|
|
11
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
11
12
|
import type {
|
|
12
13
|
EnvironmentHandlerView,
|
|
13
14
|
InfraEngine,
|
|
@@ -351,10 +352,9 @@ async function copyAutoSetupCommand() {
|
|
|
351
352
|
{{ t('settings.infrastructure.kubernetesEngine.tokenSaved') }}
|
|
352
353
|
</span>
|
|
353
354
|
</template>
|
|
354
|
-
<
|
|
355
|
+
<SecretInput
|
|
355
356
|
v-model="apiToken"
|
|
356
|
-
|
|
357
|
-
class="font-mono"
|
|
357
|
+
class="w-full font-mono"
|
|
358
358
|
autocomplete="off"
|
|
359
359
|
:placeholder="
|
|
360
360
|
tokenStored
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// driven (see docs/initiatives/descriptor-driven-infra-forms.md).
|
|
11
11
|
import { computed, reactive, ref, watch } from 'vue'
|
|
12
12
|
import { KUBERNETES_ENV_TOKEN_SECRET_KEY } from '@cat-factory/contracts'
|
|
13
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
13
14
|
import type { ProviderConnection } from '~/types/providerConnections'
|
|
14
15
|
|
|
15
16
|
const props = defineProps<{
|
|
@@ -253,7 +254,7 @@ function optional(label: string): string {
|
|
|
253
254
|
:label="t('settings.providerConnection.kubernetesEnv.apiToken')"
|
|
254
255
|
:help="t('settings.providerConnection.kubernetesEnv.apiTokenHelp')"
|
|
255
256
|
>
|
|
256
|
-
<
|
|
257
|
+
<SecretInput v-model="apiToken" class="w-full font-mono" />
|
|
257
258
|
</UFormField>
|
|
258
259
|
|
|
259
260
|
<!-- Manifest source: where the per-PR resources are read from. -->
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { computed, ref, watch } from 'vue'
|
|
9
9
|
import { LOCAL_RUNNER_DEFAULTS, LOCAL_RUNNER_LABELS, type LocalRunner } from '~/types/localModels'
|
|
10
10
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
11
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
11
12
|
|
|
12
13
|
const { t } = useI18n()
|
|
13
14
|
const ui = useUiStore()
|
|
@@ -276,9 +277,8 @@ async function remove(p: LocalRunner) {
|
|
|
276
277
|
</UFormField>
|
|
277
278
|
|
|
278
279
|
<UFormField :label="t('settings.localModelEndpoints.apiKeyOptional')">
|
|
279
|
-
<
|
|
280
|
+
<SecretInput
|
|
280
281
|
v-model="apiKey"
|
|
281
|
-
type="password"
|
|
282
282
|
class="font-mono"
|
|
283
283
|
:placeholder="
|
|
284
284
|
existing?.hasApiKey
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { computed, reactive, ref, watch } from 'vue'
|
|
8
8
|
import type { ObservabilityProviderKind } from '~/types/releaseHealth'
|
|
9
9
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
10
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
10
11
|
|
|
11
12
|
const { t } = useI18n()
|
|
12
13
|
const ui = useUiStore()
|
|
@@ -194,17 +195,11 @@ const connectedLabel = computed(() => {
|
|
|
194
195
|
<UInput v-model="datadog.site" placeholder="datadoghq.com" class="w-full" />
|
|
195
196
|
</UFormField>
|
|
196
197
|
<UFormField :label="t('settings.observabilityConnection.datadog.apiKey')">
|
|
197
|
-
<
|
|
198
|
-
v-model="datadog.apiKey"
|
|
199
|
-
type="password"
|
|
200
|
-
placeholder="DD-API-KEY"
|
|
201
|
-
class="w-full"
|
|
202
|
-
/>
|
|
198
|
+
<SecretInput v-model="datadog.apiKey" placeholder="DD-API-KEY" class="w-full" />
|
|
203
199
|
</UFormField>
|
|
204
200
|
<UFormField :label="t('settings.observabilityConnection.datadog.appKey')">
|
|
205
|
-
<
|
|
201
|
+
<SecretInput
|
|
206
202
|
v-model="datadog.appKey"
|
|
207
|
-
type="password"
|
|
208
203
|
placeholder="DD-APPLICATION-KEY"
|
|
209
204
|
class="w-full"
|
|
210
205
|
/>
|
|
@@ -254,7 +249,7 @@ const connectedLabel = computed(() => {
|
|
|
254
249
|
</p>
|
|
255
250
|
|
|
256
251
|
<UFormField :label="t('settings.observabilityConnection.incident.pagerDutyToken')">
|
|
257
|
-
<
|
|
252
|
+
<SecretInput v-model="pagerDuty.apiToken" class="w-full" />
|
|
258
253
|
</UFormField>
|
|
259
254
|
<UFormField :label="t('settings.observabilityConnection.incident.pagerDutyFromEmail')">
|
|
260
255
|
<UInput
|
|
@@ -265,7 +260,7 @@ const connectedLabel = computed(() => {
|
|
|
265
260
|
/>
|
|
266
261
|
</UFormField>
|
|
267
262
|
<UFormField :label="t('settings.observabilityConnection.incident.incidentIoKey')">
|
|
268
|
-
<
|
|
263
|
+
<SecretInput v-model="incidentIo.apiKey" class="w-full" />
|
|
269
264
|
</UFormField>
|
|
270
265
|
|
|
271
266
|
<div class="flex gap-2">
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { computed, ref, watch } from 'vue'
|
|
11
11
|
import type { OpenRouterModelMeta } from '~/types/openrouter'
|
|
12
12
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
13
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
13
14
|
|
|
14
15
|
const { t } = useI18n()
|
|
15
16
|
const ui = useUiStore()
|
|
@@ -276,11 +277,10 @@ function manageKeys() {
|
|
|
276
277
|
</UFormField>
|
|
277
278
|
</div>
|
|
278
279
|
<UFormField :label="t('settings.openRouterCatalog.apiKey')">
|
|
279
|
-
<
|
|
280
|
+
<SecretInput
|
|
280
281
|
v-model="keyValue"
|
|
281
|
-
:rows="2"
|
|
282
282
|
placeholder="paste your OpenRouter key (sk-or-…)"
|
|
283
|
-
class="font-mono"
|
|
283
|
+
class="w-full font-mono"
|
|
284
284
|
/>
|
|
285
285
|
</UFormField>
|
|
286
286
|
<div class="flex justify-end">
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { computed, reactive, ref, watch } from 'vue'
|
|
8
8
|
import type { PackageRegistryVendor } from '~/types/packageRegistries'
|
|
9
9
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
10
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
10
11
|
|
|
11
12
|
const { t } = useI18n()
|
|
12
13
|
const ui = useUiStore()
|
|
@@ -199,12 +200,7 @@ async function removeEntry(entryId: string) {
|
|
|
199
200
|
</UFormField>
|
|
200
201
|
|
|
201
202
|
<UFormField :label="t('settings.packageRegistries.add.token')">
|
|
202
|
-
<
|
|
203
|
-
v-model="form.token"
|
|
204
|
-
type="password"
|
|
205
|
-
class="w-full"
|
|
206
|
-
data-testid="package-registry-token"
|
|
207
|
-
/>
|
|
203
|
+
<SecretInput v-model="form.token" class="w-full" data-testid="package-registry-token" />
|
|
208
204
|
</UFormField>
|
|
209
205
|
|
|
210
206
|
<UButton
|
|
@@ -18,6 +18,7 @@ import { computed, ref, watch } from 'vue'
|
|
|
18
18
|
import * as v from 'valibot'
|
|
19
19
|
import { environmentManifestSchema, runnerPoolManifestSchema } from '@cat-factory/contracts'
|
|
20
20
|
import type { ProviderConnectionKind } from '~/types/providerConnections'
|
|
21
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
21
22
|
|
|
22
23
|
const props = defineProps<{
|
|
23
24
|
kind: ProviderConnectionKind
|
|
@@ -255,9 +256,8 @@ function onSave() {
|
|
|
255
256
|
</p>
|
|
256
257
|
</template>
|
|
257
258
|
<UFormField v-for="key in secretKeys" :key="key" :label="key">
|
|
258
|
-
<
|
|
259
|
+
<SecretInput
|
|
259
260
|
v-model="secrets[key]"
|
|
260
|
-
type="password"
|
|
261
261
|
class="w-full font-mono"
|
|
262
262
|
autocomplete="off"
|
|
263
263
|
:data-testid="`manifest-editor-secret-${key}`"
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { computed, ref, watch } from 'vue'
|
|
8
8
|
import type { ProviderConfigField, UserSecretKind } from '~/types/userSecrets'
|
|
9
9
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
10
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
10
11
|
|
|
11
12
|
const { t } = useI18n()
|
|
12
13
|
const ui = useUiStore()
|
|
@@ -207,9 +208,9 @@ async function remove() {
|
|
|
207
208
|
"
|
|
208
209
|
:help="field.help"
|
|
209
210
|
>
|
|
210
|
-
<
|
|
211
|
+
<SecretInput
|
|
211
212
|
v-model="values[field.key]"
|
|
212
|
-
:
|
|
213
|
+
:secret="!!field.secret"
|
|
213
214
|
class="font-mono"
|
|
214
215
|
:placeholder="field.placeholder"
|
|
215
216
|
/>
|
|
@@ -8,6 +8,7 @@ import { computed, reactive, ref, watch } from 'vue'
|
|
|
8
8
|
import type { NotificationType } from '~/types/notifications'
|
|
9
9
|
import type { SlackMemberMappingEntry, SlackMemberRole, SlackRoute } from '~/types/slack'
|
|
10
10
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
11
|
+
import SecretInput from '~/components/common/SecretInput.vue'
|
|
11
12
|
|
|
12
13
|
const ui = useUiStore()
|
|
13
14
|
const slack = useSlackStore()
|
|
@@ -188,13 +189,7 @@ async function saveMapping() {
|
|
|
188
189
|
{{ t('slack.connect.orPasteToken') }}
|
|
189
190
|
</span>
|
|
190
191
|
<div class="flex gap-2">
|
|
191
|
-
<
|
|
192
|
-
v-model="tokenInput"
|
|
193
|
-
size="sm"
|
|
194
|
-
class="flex-1"
|
|
195
|
-
type="password"
|
|
196
|
-
placeholder="xoxb-…"
|
|
197
|
-
/>
|
|
192
|
+
<SecretInput v-model="tokenInput" size="sm" class="flex-1" placeholder="xoxb-…" />
|
|
198
193
|
<UButton
|
|
199
194
|
color="primary"
|
|
200
195
|
variant="soft"
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
RequirementPriority,
|
|
13
13
|
SpecModule,
|
|
14
14
|
} from '~/types/spec'
|
|
15
|
+
import IconButton from '~/components/common/IconButton.vue'
|
|
15
16
|
|
|
16
17
|
const { t } = useI18n()
|
|
17
18
|
const board = useBoardStore()
|
|
@@ -176,7 +177,14 @@ function kindLabel(item: RequirementItem): string {
|
|
|
176
177
|
{{ t('spec.mode.gherkin') }}
|
|
177
178
|
</UButton>
|
|
178
179
|
</div>
|
|
179
|
-
<
|
|
180
|
+
<IconButton
|
|
181
|
+
icon="i-lucide-x"
|
|
182
|
+
color="neutral"
|
|
183
|
+
variant="ghost"
|
|
184
|
+
size="sm"
|
|
185
|
+
:label="t('common.close')"
|
|
186
|
+
@click="close"
|
|
187
|
+
/>
|
|
180
188
|
</div>
|
|
181
189
|
</header>
|
|
182
190
|
|
|
@@ -286,7 +286,7 @@ const canApprove = computed(
|
|
|
286
286
|
v-model="perViewNotes[p.view]"
|
|
287
287
|
rows="2"
|
|
288
288
|
:placeholder="t('visualConfirm.notePlaceholder', { view: p.view })"
|
|
289
|
-
class="mt-1 w-full rounded-md border border-slate-700 bg-slate-950 px-2 py-1.5 text-[12px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none"
|
|
289
|
+
class="mt-1 w-full rounded-md border border-slate-700 bg-slate-950 px-2 py-1.5 text-[12px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/60"
|
|
290
290
|
/>
|
|
291
291
|
</div>
|
|
292
292
|
</div>
|
|
@@ -340,7 +340,7 @@ const canApprove = computed(
|
|
|
340
340
|
v-model="globalFindings"
|
|
341
341
|
rows="3"
|
|
342
342
|
:placeholder="t('visualConfirm.requestFix.placeholder')"
|
|
343
|
-
class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none"
|
|
343
|
+
class="w-full rounded-md border border-slate-700 bg-slate-950 px-3 py-2 text-[13px] text-slate-200 placeholder:text-slate-600 focus:border-amber-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/60"
|
|
344
344
|
/>
|
|
345
345
|
<div class="mt-2 flex items-center justify-between">
|
|
346
346
|
<span class="text-[11px] text-slate-500">
|
package/i18n/locales/en.json
CHANGED
package/i18n/locales/es.json
CHANGED
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"reconfigureHint": "Puedes volver a configurarlo más tarde."
|
|
41
41
|
},
|
|
42
42
|
"copied": "Copiado al portapapeles",
|
|
43
|
+
"reveal": "Mostrar",
|
|
44
|
+
"hide": "Ocultar",
|
|
43
45
|
"disconnect": "Desconectar",
|
|
44
46
|
"copyFailed": "No se pudo copiar al portapapeles",
|
|
45
47
|
"revoke": "Revocar",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"reconfigureHint": "Vous pourrez le reconfigurer plus tard."
|
|
41
41
|
},
|
|
42
42
|
"copied": "Copié dans le presse-papiers",
|
|
43
|
+
"reveal": "Afficher",
|
|
44
|
+
"hide": "Masquer",
|
|
43
45
|
"disconnect": "Déconnecter",
|
|
44
46
|
"copyFailed": "Échec de la copie dans le presse-papiers",
|
|
45
47
|
"revoke": "Révoquer",
|
package/i18n/locales/he.json
CHANGED
package/i18n/locales/ja.json
CHANGED
package/i18n/locales/pl.json
CHANGED
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"reconfigureHint": "Możesz skonfigurować to ponownie później."
|
|
41
41
|
},
|
|
42
42
|
"copied": "Skopiowano do schowka",
|
|
43
|
+
"reveal": "Pokaż",
|
|
44
|
+
"hide": "Ukryj",
|
|
43
45
|
"disconnect": "Rozłącz",
|
|
44
46
|
"copyFailed": "Nie udało się skopiować do schowka",
|
|
45
47
|
"revoke": "Unieważnij",
|
package/i18n/locales/tr.json
CHANGED
package/i18n/locales/uk.json
CHANGED
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"reconfigureHint": "Ви зможете налаштувати це знову пізніше."
|
|
41
41
|
},
|
|
42
42
|
"copied": "Скопійовано в буфер обміну",
|
|
43
|
+
"reveal": "Показати",
|
|
44
|
+
"hide": "Приховати",
|
|
43
45
|
"disconnect": "Від'єднати",
|
|
44
46
|
"copyFailed": "Не вдалося скопіювати до буфера обміну",
|
|
45
47
|
"revoke": "Відкликати",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.96.
|
|
3
|
+
"version": "0.96.4",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"valibot": "^1.4.2",
|
|
35
35
|
"vue": "3.5.39",
|
|
36
36
|
"wretch": "^3.0.9",
|
|
37
|
-
"@cat-factory/contracts": "0.
|
|
37
|
+
"@cat-factory/contracts": "0.107.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|