signup: an unsolved anti-spam check submits the form and clears everything typed #162

Stängd
öppnade 2026-08-02 12:30:26 +00:00 av supernaut · 0 kommentarer
Ägare

What happens

Submit the sign-up form without solving the anti-spam check. The form posts, the server rejects it,
and you land back on an empty form: username, email, account type and the accepted-terms tick are
all gone and have to be retyped.

Why

/api/signup answers every outcome with a 303 back to /signup?status=…
(src/pages/api/signup.ts:67-73), and the re-rendered form echoes nothing — none of the fields
carry a value or checked (src/components/signup-form.astro:195-276). So the response is a
fresh, empty page. /signup is also no-store (src/pages/signup.astro:20-22), so pressing Back
does not reliably restore what was typed either.

Every other control on the form has a native constraint (required, pattern, type="email"), so
the browser blocks an incomplete submit before it leaves the page. The captcha is the only control
that does not — which is why it is the one that looks like it wipes the form.

Fix

cap-widget is a form-associated custom element and already implements this. It sets
valueMissing validity when it carries the required attribute and has no token, and on the
resulting invalid event it scrolls itself into view and flashes.

  1. Add required and data-cap-i18n-required-label={t("signup.captcha.required")} to both
    <cap-widget> elements — the sign-up form (src/components/signup-form.astro:293) and the
    re-issue form (:175).
  2. Add the signup.captcha.required string in both languages
    (EN "Complete the anti-spam check before you continue." /
    SV "Slutför antispam-kontrollen innan du fortsätter.").
  3. The validity anchor sits in the widget's shadow root, so the native bubble is not guaranteed
    everywhere and the flash is visual only. Add an always-rendered, initially hidden
    <p id="signup-captcha-required" role="alert"> inside .captcha, unhidden from an invalid
    listener in a bundled module script (script-src 'self' already covers that pattern — see the
    focus script at src/components/signup-form.astro:350), and append its id to the wrapper's
    aria-describedby.

Out of scope

Statuses decided on the server (taken, ratelimited, error, nomail) still lose the typed
values. Tracked separately.

Done when

  • Submitting with an unsolved check does not navigate, and points the user at the widget with a
    message in the page language.
  • The message is announced to a screen reader, not only flashed.
  • Both the sign-up and the re-issue form behave the same way.
  • pnpm check, pnpm lint and pnpm lang-check pass.

Part of gitborg/gitborg-docs#69.

## What happens Submit the sign-up form without solving the anti-spam check. The form posts, the server rejects it, and you land back on an empty form: username, email, account type and the accepted-terms tick are all gone and have to be retyped. ## Why `/api/signup` answers every outcome with a 303 back to `/signup?status=…` (`src/pages/api/signup.ts:67-73`), and the re-rendered form echoes nothing — none of the fields carry a `value` or `checked` (`src/components/signup-form.astro:195-276`). So the response is a fresh, empty page. `/signup` is also `no-store` (`src/pages/signup.astro:20-22`), so pressing Back does not reliably restore what was typed either. Every other control on the form has a native constraint (`required`, `pattern`, `type="email"`), so the browser blocks an incomplete submit before it leaves the page. The captcha is the only control that does not — which is why it is the one that looks like it wipes the form. ## Fix `cap-widget` is a form-associated custom element and already implements this. It sets `valueMissing` validity when it carries the `required` attribute and has no token, and on the resulting `invalid` event it scrolls itself into view and flashes. 1. Add `required` and `data-cap-i18n-required-label={t("signup.captcha.required")}` to both `<cap-widget>` elements — the sign-up form (`src/components/signup-form.astro:293`) and the re-issue form (`:175`). 2. Add the `signup.captcha.required` string in both languages (EN "Complete the anti-spam check before you continue." / SV "Slutför antispam-kontrollen innan du fortsätter."). 3. The validity anchor sits in the widget's shadow root, so the native bubble is not guaranteed everywhere and the flash is visual only. Add an always-rendered, initially hidden `<p id="signup-captcha-required" role="alert">` inside `.captcha`, unhidden from an `invalid` listener in a bundled module script (`script-src 'self'` already covers that pattern — see the focus script at `src/components/signup-form.astro:350`), and append its id to the wrapper's `aria-describedby`. ## Out of scope Statuses decided on the server (`taken`, `ratelimited`, `error`, `nomail`) still lose the typed values. Tracked separately. ## Done when - Submitting with an unsolved check does not navigate, and points the user at the widget with a message in the page language. - The message is announced to a screen reader, not only flashed. - Both the sign-up and the re-issue form behave the same way. - `pnpm check`, `pnpm lint` and `pnpm lang-check` pass. Part of gitborg/gitborg-docs#69.
supernaut lade till detta till projektet Bitborg Web 2026-08-02 12:34:27 +00:00
Logga in för att delta i denna konversation.
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-web#162
Ingen beskrivning angiven.