From ed64aca63bf1b52d6598e0989099f14626bd2968 Mon Sep 17 00:00:00 2001 From: Mitchell-Johnson Date: Sun, 27 Sep 2026 18:03:26 -0700 Subject: [PATCH] An eye on the login form, to see what is being typed A guest password is read off a message and typed by hand, and from the login form a mistyped character and a wrong password look identical. Revealed, the field is a text field, so it also turns off capitalisation and autocorrect, which a phone keyboard would otherwise offer for it. Co-Authored-By: Claude Opus 5 --- web/src/Login.tsx | 29 +++++++++++++++++++++++++++-- web/src/styles.css | 6 ++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/web/src/Login.tsx b/web/src/Login.tsx index 2c9cea4..ccb6e8b 100644 --- a/web/src/Login.tsx +++ b/web/src/Login.tsx @@ -2,6 +2,19 @@ import { FormEvent, useState } from 'react' import { api } from './api' import { Spinner } from './ui' +// Drawn, not typed: 👁 is an emoji on phones, full colour and its own size. Outline strokes +// match the glyphs in the rail and the power button. +const EyeIcon = ( + +) +const EyeOffIcon = ( + +) + const USER_KEY = 'hamlab.user' // the last account signed in on this device, offered again export default function Login({ onLogin }: { onLogin: () => void }) { @@ -9,6 +22,9 @@ export default function Login({ onLogin }: { onLogin: () => void }) { const [pw, setPw] = useState('') const [err, setErr] = useState('') const [busy, setBusy] = useState(false) + // Shown on request: a generated password is typed by hand off a message, and a wrong + // character looks exactly like a wrong password from here. + const [show, setShow] = useState(false) async function submit(e: FormEvent) { e.preventDefault() @@ -26,8 +42,17 @@ export default function Login({ onLogin }: { onLogin: () => void }) { Ham Lab setUser(e.target.value)} disabled={busy} /> - setPw(e.target.value)} disabled={busy} /> +
+ {/* revealed, the field is a text field, so the keyboard would otherwise offer to + capitalise and correct what is typed into it */} + setPw(e.target.value)} disabled={busy} /> + +
{err &&
{err}
} diff --git a/web/src/styles.css b/web/src/styles.css index bb8535a..2a58752 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -20,6 +20,12 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi .login input { padding: 10px 12px; background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 16px; } .login button, .primary { padding: 10px 12px; background: var(--accent); color: var(--btn-fg); border-radius: 6px; font-weight: 600; } .login button:disabled { opacity: .5; } +/* the password field and its show/hide eye, which sits inside the field's right edge */ +.login .pwfield { position: relative; display: flex; } +.login .pwfield input { flex: 1; min-width: 0; padding-right: 44px; } +.login button.pweye { position: absolute; right: 1px; top: 1px; bottom: 1px; width: 42px; padding: 0; background: none; color: var(--muted); font-weight: 400; border-radius: 0 6px 6px 0; } +.login button.pweye:hover, .login button.pweye[aria-pressed="true"] { color: var(--fg); } +.login button.pweye:disabled { opacity: .4; } .error { color: var(--bad); font-size: 13px; } /* layout */