@cat-factory/app 0.96.3 → 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.
Files changed (28) hide show
  1. package/app/components/auth/LoginScreen.vue +3 -4
  2. package/app/components/auth/ResetPasswordScreen.vue +3 -4
  3. package/app/components/common/SecretInput.vue +49 -0
  4. package/app/components/documents/DocumentSourceConnectModal.vue +3 -2
  5. package/app/components/layout/AccountDeploymentSettings.vue +7 -12
  6. package/app/components/layout/AccountTeamSettings.vue +2 -2
  7. package/app/components/providers/ApiKeysSection.vue +3 -3
  8. package/app/components/providers/PersonalCredentialModal.vue +2 -2
  9. package/app/components/providers/PersonalSubscriptionSection.vue +4 -5
  10. package/app/components/providers/VendorCredentialsModal.vue +3 -3
  11. package/app/components/settings/KubernetesEngineForm.vue +3 -3
  12. package/app/components/settings/KubernetesEnvironmentForm.vue +2 -1
  13. package/app/components/settings/LocalModelEndpointsPanel.vue +2 -2
  14. package/app/components/settings/ObservabilityConnectionPanel.vue +5 -10
  15. package/app/components/settings/OpenRouterCatalogPanel.vue +3 -3
  16. package/app/components/settings/PackageRegistriesPanel.vue +2 -6
  17. package/app/components/settings/ProviderManifestEditor.vue +2 -2
  18. package/app/components/settings/UserSecretsSection.vue +3 -2
  19. package/app/components/slack/SlackPanel.vue +2 -7
  20. package/i18n/locales/en.json +2 -0
  21. package/i18n/locales/es.json +2 -0
  22. package/i18n/locales/fr.json +2 -0
  23. package/i18n/locales/he.json +2 -0
  24. package/i18n/locales/ja.json +2 -0
  25. package/i18n/locales/pl.json +2 -0
  26. package/i18n/locales/tr.json +2 -0
  27. package/i18n/locales/uk.json +2 -0
  28. package/package.json +2 -2
@@ -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
- <UInput
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
- <UInput
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
- <UInput
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
- <UInput
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"
@@ -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
- <UInput
107
+ <SecretInput
107
108
  v-model="values[field.key]"
108
- :type="field.secret ? 'password' : 'text'"
109
+ :secret="!!field.secret"
109
110
  :placeholder="field.placeholder"
110
111
  class="w-full"
111
112
  />
@@ -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
- <UInput
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
- <UInput
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
- <UInput
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
- <UInput
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
- <UInput
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
- <UInput
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
- <UInput
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
  />
@@ -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
- <UTextarea
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
- <UInput
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
- <UTextarea
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
- <UInput
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
- <UTextarea
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">
@@ -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
- <UInput
355
+ <SecretInput
355
356
  v-model="apiToken"
356
- type="password"
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
- <UInput v-model="apiToken" type="password" class="font-mono" />
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
- <UInput
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
- <UInput
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
- <UInput
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
- <UInput v-model="pagerDuty.apiToken" type="password" class="w-full" />
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
- <UInput v-model="incidentIo.apiKey" type="password" class="w-full" />
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
- <UTextarea
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
- <UInput
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
- <UInput
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
- <UInput
211
+ <SecretInput
211
212
  v-model="values[field.key]"
212
- :type="field.secret ? 'password' : 'text'"
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
- <UInput
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"
@@ -21,6 +21,8 @@
21
21
  "retry": "Retry",
22
22
  "copy": "Copy",
23
23
  "copied": "Copied to clipboard",
24
+ "reveal": "Reveal",
25
+ "hide": "Hide",
24
26
  "delete": "Delete",
25
27
  "remove": "Remove",
26
28
  "confirm": {
@@ -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",
@@ -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",
@@ -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": "לבטל",
@@ -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": "取り消す",
@@ -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",
@@ -40,6 +40,8 @@
40
40
  "reconfigureHint": "Bunu daha sonra yeniden yapılandırabilirsiniz."
41
41
  },
42
42
  "copied": "Panoya kopyalandı",
43
+ "reveal": "Göster",
44
+ "hide": "Gizle",
43
45
  "disconnect": "Bağlantıyı kes",
44
46
  "copyFailed": "Panoya kopyalanamadı",
45
47
  "revoke": "İptal et",
@@ -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",
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.106.0"
37
+ "@cat-factory/contracts": "0.107.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",