From ced09ec08d85d8770b294006159ea5e5a0218ba8 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Wed, 16 Sep 2026 14:08:23 +0000 Subject: [PATCH] Account panel: a "Forgot password?" link to the reset page nixamp.com already serves PR #181 built password recovery (hashed single-use links, Resend mail, session revocation) and put a "Forgot password?" link on the BackToSchool sign-in, but nixamp.com's own account panel never linked to it. A member whose password no longer matched read "that email and password do not match an account" with nowhere to go, although /reset-password was live on the same origin. The link sits next to "Create one", hides while creating an account, and opens in its own tab inside a classroom embed so the host page's iframe is not navigated away. Co-Authored-By: Claude Fable 5.1 --- web/index.html | 3 +++ web/src/app.ts | 5 +++++ web/src/styles.css | 3 +++ 3 files changed, 11 insertions(+) diff --git a/web/index.html b/web/index.html index 90186f5..d1ceedd 100644 --- a/web/index.html +++ b/web/index.html @@ -288,6 +288,9 @@

Now Playing

placeholder="password" data-i18n-placeholder="password" aria-label="Password" data-i18n-aria-label="Password" /> + + Forgot password? diff --git a/web/src/app.ts b/web/src/app.ts index 2962bec..e0fd1d9 100644 --- a/web/src/app.ts +++ b/web/src/app.ts @@ -188,6 +188,7 @@ export function start(): void { accountPassword: need("account-password"), accountSubmit: need("account-submit"), accountToggle: need("account-toggle"), + accountForgot: need("account-forgot"), accountProviders: need("account-providers"), accountPanel: need("account-panel"), accountElsewhere: need("account-elsewhere"), @@ -5580,6 +5581,10 @@ export function start(): void { uiText(dom.accountSubmit, () => creating ? uiMessage("Create account") : uiMessage("Sign in")); uiText(dom.accountToggle, () => creating ? "I have one" : uiMessage("Create one")); dom.accountPassword.autocomplete = creating ? "new-password" : "current-password"; + // Nothing to forget while making an account. Inside a classroom embed + // the page is somebody else's iframe, so recovery opens in its own tab. + dom.accountForgot.hidden = creating; + if (classroomEmbed) { dom.accountForgot.target = "_blank"; dom.accountForgot.rel = "noopener"; } showWelcome(); }; diff --git a/web/src/styles.css b/web/src/styles.css index 154346c..e6af70b 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -938,6 +938,9 @@ button:focus-visible, input:focus-visible, a:focus-visible { outline: 2px solid button.primary { color: var(--green); border-color: var(--green-dim); min-width: 64px; } button.ghost { background: transparent; color: var(--muted); } +/* A link dressed as a ghost button, so "Forgot password?" sits with "Create one". */ +a.ghost { color: var(--muted); font-size: 0.9em; text-decoration: none; padding: 6px 4px; } +a.ghost:hover { color: var(--green); text-decoration: underline; } /* Share, with the transport: the one button there that is for somebody else. */ button.share { color: var(--accent); border-color: var(--edge); letter-spacing: 0.04em; } button.share:hover { background: #16241c; }