aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-20 16:35:46 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-20 16:35:55 +0200
commitbe50f1442148c21cabf039abdcae5fe20bc690e8 (patch)
tree28a7cb040d9a34dd242c05013179c1829048050e /packages/meshbay-hub
parent2203618c8202bd95302215d7220f2de05fe12e48 (diff)
downloadmeshbay-be50f1442148c21cabf039abdcae5fe20bc690e8.tar.gz
feat(hub): reveal button in the sign-in and registration passphrase fields
One PasswordInput for all three fields, with an eye toggle inside the box. Out of the tab order, type="button" so a click cannot submit the form. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/auth-page.js47
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/icon.js6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/de.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/en.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/es.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css26
13 files changed, 90 insertions, 9 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js
index 8958d44..f0af187 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js
@@ -14,6 +14,35 @@ const PASSWORD_MIN_LEN = 12;
// before a passphrase derivation rather than after it.
const USERNAME_MIN_LEN = 8;
+// ── Passphrase field ─────────────────────────────────────────────────────────
+//
+// A passphrase this long is mistyped often enough that checking it is worth a
+// control, and the alternative people reach for otherwise is typing it into
+// the username box to read it back. The button is out of the tab order
+// (`tabindex="-1"`): everyone who is not reaching for it would pay a keystroke
+// between the passphrase and the submit button, and it does nothing a keyboard
+// user cannot do by other means. `type="button"` matters — a bare button in a
+// form submits it, which here would try to sign in on the first click.
+function PasswordInput({
+ value, onInput, placeholder, autocomplete,
+ minlength = null, autofocus = false,
+}) {
+ const [shown, setShown] = useState(false);
+ const label = t(shown ? 'login.hide_password' : 'login.show_password');
+ return html`
+ <div class="pw-field">
+ <input type=${shown ? 'text' : 'password'} placeholder=${placeholder}
+ value=${value} onInput=${onInput} autocomplete=${autocomplete}
+ required minlength=${minlength} autofocus=${autofocus} />
+ <button type="button" class="pw-toggle" tabindex="-1"
+ title=${label} aria-label=${label} aria-pressed=${shown}
+ onClick=${() => setShown(v => !v)}>
+ <${Icon} name=${shown ? 'eyeoff' : 'eye'} />
+ </button>
+ </div>
+ `;
+}
+
// ── reCAPTCHA v2 helper ──────────────────────────────────────────────────────
let _captchaSiteKey = null;
@@ -183,9 +212,9 @@ export function LoginPage({ onLogin }) {
<input type="text" placeholder="${t('login.username')}" value=${username}
onInput=${e => setUsername(e.target.value)}
autocomplete="username" required autofocus />
- <input type="password" placeholder="${t('login.password')}" value=${password}
- onInput=${e => setPassword(e.target.value)}
- autocomplete="current-password" required />
+ <${PasswordInput} placeholder=${t('login.password')} value=${password}
+ onInput=${e => setPassword(e.target.value)}
+ autocomplete="current-password" />
${error && html`<div class="error-msg">${error}</div>`}
${pendingVerif && html`
<div class="error-msg" style="background:var(--bg-secondary);border-left:3px solid var(--yellow, #f59e0b)">
@@ -474,9 +503,9 @@ export function RegisterPage() {
<input type="email" placeholder="${t('register.email')}" value=${email}
onInput=${e => setEmail(e.target.value)}
autocomplete="email" required />
- <input type="password" placeholder="${t('register.password')}" value=${password}
- onInput=${e => setPassword(e.target.value)}
- autocomplete="new-password" required minlength="8" />
+ <${PasswordInput} placeholder=${t('register.password')} value=${password}
+ onInput=${e => setPassword(e.target.value)}
+ autocomplete="new-password" minlength="8" />
<div style=${`margin:-4px 0 10px;${password ? '' : 'visibility:hidden;height:0;margin:0;overflow:hidden'}`}>
<div style="height:4px;background:var(--border);border-radius:2px;overflow:hidden">
<div style=${`height:100%;width:${Math.min(100, passwordBits(password) / 100 * 100)}%;
@@ -487,9 +516,9 @@ export function RegisterPage() {
${t('register.strength', { bits: passwordBits(password) })}
</p>
</div>
- <input type="password" placeholder="${t('register.confirm')}" value=${confirm}
- onInput=${e => setConfirm(e.target.value)}
- autocomplete="new-password" required />
+ <${PasswordInput} placeholder=${t('register.confirm')} value=${confirm}
+ onInput=${e => setConfirm(e.target.value)}
+ autocomplete="new-password" />
<label style="display:flex; gap:8px; align-items:flex-start; margin:4px 0 2px;
font-size:0.88em; color:var(--text-secondary)">
<input type="checkbox" checked=${emailRecovery}
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/icon.js b/packages/meshbay-hub/src/meshbay_hub/static/icon.js
index 94a82fb..c80258c 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/icon.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/icon.js
@@ -29,6 +29,12 @@ const ICON_PATHS = {
play: ['M8 5.5v13l11-6.5z'],
eye: ['M2 12s3.6-6.5 10-6.5S22 12 22 12s-3.6 6.5-10 6.5S2 12 2 12',
'M12 14.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5'],
+ // The same eye under a stroke. Drawn as `eye` plus the slash rather than as
+ // a broken lid, so the two states of a reveal button are the same glyph and
+ // the toggle reads as one control.
+ eyeoff: ['M2 12s3.6-6.5 10-6.5S22 12 22 12s-3.6 6.5-10 6.5S2 12 2 12',
+ 'M12 14.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5',
+ 'M4 4l16 16'],
trash: ['M4 7h16', 'M10 11v6M14 11v6',
'M6 7l1 12.5A1.5 1.5 0 0 0 8.5 21h7a1.5 1.5 0 0 0 1.5-1.5L18 7',
'M9.5 7V5a1.5 1.5 0 0 1 1.5-1.5h2A1.5 1.5 0 0 1 14.5 5v2'],
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
index 47f0b31..989b5ca 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -36,6 +36,8 @@ export default {
'login.no_account': 'Noch kein Konto?',
'login.register_link': 'Konto erstellen',
'login.forgot': 'Passphrase vergessen?',
+ 'login.show_password': 'Passphrase anzeigen',
+ 'login.hide_password': 'Passphrase verbergen',
'reset.title': 'Passphrase zurücksetzen',
'reset.request_intro': 'Geben Sie Ihren Benutzernamen und die E-Mail des Kontos ein. Stimmen sie überein, wird ein Zurücksetzungscode an diese Adresse gesendet.',
'reset.send_code': 'Code senden',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
index 92a45dd..5042b31 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -39,6 +39,8 @@ export default {
'login.no_account': 'No account?',
'login.register_link': 'Register',
'login.forgot': 'Forgot your passphrase?',
+ 'login.show_password': 'Show passphrase',
+ 'login.hide_password': 'Hide passphrase',
'reset.title': 'Reset passphrase',
'reset.request_intro': 'Enter your username and the email on the account. If they match, a reset code is sent to it.',
'reset.send_code': 'Send code',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
index ae2a405..80beb15 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -35,6 +35,8 @@ export default {
'login.no_account': '¿No tiene cuenta?',
'login.register_link': 'Crear una cuenta',
'login.forgot': '¿Olvidó su frase de contraseña?',
+ 'login.show_password': 'Mostrar la frase de contraseña',
+ 'login.hide_password': 'Ocultar la frase de contraseña',
'reset.title': 'Restablecer la frase de contraseña',
'reset.request_intro': 'Introduzca su nombre de usuario y el correo de la cuenta. Si coinciden, se envía un código de restablecimiento a esa dirección.',
'reset.send_code': 'Enviar código',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
index 795a06c..bc2e221 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -35,6 +35,8 @@ export default {
'login.no_account': 'Pas encore de compte ?',
'login.register_link': 'Créer un compte',
'login.forgot': 'Phrase secrète oubliée ?',
+ 'login.show_password': 'Afficher la phrase secrète',
+ 'login.hide_password': 'Masquer la phrase secrète',
'reset.title': 'Réinitialiser la phrase secrète',
'reset.request_intro': 'Saisissez votre nom d’utilisateur et l’e-mail du compte. S’ils correspondent, un code de réinitialisation est envoyé à cette adresse.',
'reset.send_code': 'Envoyer le code',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
index 2cb1a31..34296e1 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -36,6 +36,8 @@ export default {
'login.no_account': 'Non ha ancora un account?',
'login.register_link': 'Crea un account',
'login.forgot': 'Ha dimenticato la passphrase?',
+ 'login.show_password': 'Mostra la passphrase',
+ 'login.hide_password': 'Nascondi la passphrase',
'reset.title': 'Reimposta la passphrase',
'reset.request_intro': 'Inserisca il suo nome utente e l’e-mail dell’account. Se corrispondono, un codice di reimpostazione viene inviato a quell’indirizzo.',
'reset.send_code': 'Invia codice',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
index e26110f..d256d1b 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -36,6 +36,8 @@ export default {
'login.no_account': 'アカウントをお持ちでないですか?',
'login.register_link': 'アカウントを作成',
'login.forgot': 'パスフレーズをお忘れですか?',
+ 'login.show_password': 'パスフレーズを表示',
+ 'login.hide_password': 'パスフレーズを非表示',
'reset.title': 'パスフレーズをリセット',
'reset.request_intro': 'ユーザー名とアカウントのメールアドレスを入力してください。両方が一致する場合、そのアドレスにリセットコードを送信します。',
'reset.send_code': 'コードを送信',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
index ce3cdf9..2c5c854 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -36,6 +36,8 @@ export default {
'login.no_account': 'Nog geen account?',
'login.register_link': 'Account aanmaken',
'login.forgot': 'Wachtwoordzin vergeten?',
+ 'login.show_password': 'Wachtwoordzin tonen',
+ 'login.hide_password': 'Wachtwoordzin verbergen',
'reset.title': 'Wachtwoordzin opnieuw instellen',
'reset.request_intro': 'Voer uw gebruikersnaam en het e-mailadres van het account in. Als ze overeenkomen, wordt een herstelcode naar dat adres gestuurd.',
'reset.send_code': 'Code versturen',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
index 7f347da..f2cfb8d 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -39,6 +39,8 @@ export default {
'login.no_account': 'Nie ma jeszcze konta?',
'login.register_link': 'Załóż konto',
'login.forgot': 'Nie pamiętasz hasła-frazy?',
+ 'login.show_password': 'Pokaż hasło-frazę',
+ 'login.hide_password': 'Ukryj hasło-frazę',
'reset.title': 'Zresetuj hasło-frazę',
'reset.request_intro': 'Wpisz swoją nazwę użytkownika i adres e-mail konta. Jeśli się zgadzają, kod resetowania zostanie wysłany na ten adres.',
'reset.send_code': 'Wyślij kod',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
index f0f1077..9d51a9c 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
@@ -37,6 +37,8 @@ export default {
'login.no_account': 'Ainda não tem conta?',
'login.register_link': 'Criar uma conta',
'login.forgot': 'Esqueceu sua frase secreta?',
+ 'login.show_password': 'Mostrar a frase secreta',
+ 'login.hide_password': 'Ocultar a frase secreta',
'reset.title': 'Redefinir a frase secreta',
'reset.request_intro': 'Digite seu nome de usuário e o e-mail da conta. Se coincidirem, um código de redefinição é enviado para esse endereço.',
'reset.send_code': 'Enviar código',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
index be337b7..5ca0571 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
@@ -36,6 +36,8 @@ export default {
'login.no_account': '还没有账户?',
'login.register_link': '注册',
'login.forgot': '忘记密码短语?',
+ 'login.show_password': '显示密码短语',
+ 'login.hide_password': '隐藏密码短语',
'reset.title': '重置密码短语',
'reset.request_intro': '输入您的用户名和账户的邮箱。如果二者匹配,重置码将发送至该邮箱。',
'reset.send_code': '发送验证码',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index 7c36ed7..e012a1a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -830,6 +830,32 @@ input[type="email"] {
}
input:focus { outline: none; border-color: var(--border-focus); }
+/* A reveal button inside the field rather than beside it. The input reserves
+ the room on its right, so a passphrase long enough to scroll ends under the
+ button instead of behind it. The `background: none` pair is not redundant:
+ the bare `button` rules just below paint every button with the accent colour
+ on rest and on hover, and this one has to stay part of the field. */
+.pw-field { position: relative; display: flex; }
+.pw-field input { padding-right: 40px; }
+.pw-toggle {
+ position: absolute;
+ top: 0;
+ right: 4px;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ padding: 0;
+ background: none;
+ border: none;
+ border-radius: 6px;
+ color: var(--text-dim);
+ font-size: 1.15em;
+ cursor: pointer;
+}
+.pw-toggle:hover { background: none; color: var(--text); }
+
button {
padding: 10px 20px;
background: var(--accent);