From b19fb21fc4f8b16ce0e8993a59b0c40f0013599d Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Tue, 20 Oct 2026 11:00:08 +0200 Subject: [PATCH] vault prompt: appears at once and stays put between questions The password box rose into place like the [y/N] prompt, a 380 ms slide and fade, and was taken down and put up again for every question: twice for :vault, three times for :passwd, and right after the command line closed for :unlock. Whatever was typed in that time went into a field still moving and half transparent. It now appears without an entrance, and an answer puts it away a turn later, so a question that follows straight on takes over the same box. Password managers are asked to leave the field alone; it is not the login. --- web/src/lib/SecretPrompt.svelte | 11 ++++++++++- web/src/lib/store.svelte.ts | 17 +++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/web/src/lib/SecretPrompt.svelte b/web/src/lib/SecretPrompt.svelte index 69e8436..3423951 100644 --- a/web/src/lib/SecretPrompt.svelte +++ b/web/src/lib/SecretPrompt.svelte @@ -3,6 +3,12 @@ * The password `workspace.askSecret` asks for, in the same place as the * `[y/N]` prompt. A masked field of its own rather than the command line, so * a password never shows on screen or lands in the command history. + * + * It appears at once rather than rising into place like the `[y/N]` prompt: + * it is where the next keys go, and a field still sliding and fading in + * under them reads as a stutter. Password managers are asked to leave the + * field alone: it is not the login, and their menus redrawn on every key + * are a flicker of their own. */ import { workspace } from './store.svelte' @@ -38,6 +44,10 @@ type="password" autocomplete="off" spellcheck="false" + data-1p-ignore + data-lpignore="true" + data-bwignore + data-form-type="other" bind:this={input} bind:value {onkeydown} @@ -67,7 +77,6 @@ border-bottom: 0; border-radius: var(--radius-l) var(--radius-l) 0 0; font-size: 13px; - animation: rise var(--t-slow) var(--ease-snap) both; } .text { flex: none; color: var(--light); } input { diff --git a/web/src/lib/store.svelte.ts b/web/src/lib/store.svelte.ts index 6c26c9f..84ca5ce 100644 --- a/web/src/lib/store.svelte.ts +++ b/web/src/lib/store.svelte.ts @@ -125,7 +125,7 @@ class Workspace { */ prompt = $state<{ text: string; resolve: (yes: boolean) => void } | null>(null) /** A password asked for in the same place, typed into a masked field. */ - secret = $state<{ text: string; resolve: (value: string | null) => void } | null>(null) + secret = $state.raw<{ text: string; resolve: (value: string | null) => void } | null>(null) /** The vault, as last heard from the server; null until asked. */ vault = $state(null) media = $state([]) @@ -217,13 +217,22 @@ class Workspace { askSecret(text: string): Promise { this.secret?.resolve(null) return new Promise((resolve) => { - this.secret = { + let answered = false + const asked = { text, - resolve: (value) => { - this.secret = null + resolve: (value: string | null) => { + if (answered) return + answered = true + // Put away a turn later, so a question that follows straight on — + // the second of two passwords — takes over the same box instead + // of a new one appearing under the fingers already typing it. + setTimeout(() => { + if (this.secret === asked) this.secret = null + }) resolve(value) }, } + this.secret = asked }) }