Found while double-checking /login after the redirect fix: a wrong
password left the form completely silent. Two stacked causes, both
pre-dating the CSP work:
1. The two HX-Request branches for invalid credentials returned JSON,
unlike every sibling error branch — there was no HTML partial to
show at all. They now return a shared invalidCredentialsHTML div
(the JSON shape is kept for non-htmx API callers).
2. htmx does not swap 4xx/5xx responses by default, so even the HTML
error partials that DID exist (429 lockout, 500s, registration
failures like "Email already exists") never landed in the target.
auth_forms.ts now swaps them itself on htmx:beforeSwap when the
error response is text/html — JSON errors stay ignored.
Verified live: /login success → /dashboard; wrong password shows
"Invalid email/username or password." and stays on /login; duplicate
email on /register shows "Email already exists". Gates: make test 9
pkgs, vitest 155, Playwright 20.
The htmx login/registration partials answered success with an inline
<script> that stashed the auth token and set window.location — refused
by hash-mode script-src (no 'unsafe-inline'), so the user saw "Login
successful! Redirecting..." and nothing else (reported from the home-
page login form; /login was equally broken). The same pattern lived in
collections.go (delete → redirect) and devices.go (token regen →
reload, whose script tag was also unclosed).
Replacement, per the AGENTS.md CSP rules:
- auth.go login/registration: the partial now carries data-auth-token +
data-redirect (redirect is path-only — open-redirect hygiene — and
escaped); web/src/auth_forms.ts (new, armed from main.ts on every
page) listens for htmx:afterSwap, stashes the token, and navigates.
- collections.go delete: data-redirect, same listener.
- devices.go token regen: htmx's native HX-Refresh response header.
- The dead localStorage 'user'/'refreshToken' writes are dropped — no
reader anywhere in web/src; only 'token' is load-bearing (19 modules).
Tripwire extension: internal/middleware/csp_go_sources_test.go scans
non-test Go sources for literal <script> tags and native on*= attributes
(hand-written files only — templ codegen is owned by the .templ scan),
verified to fire on a reintroduced probe. AGENTS.md CSP section notes
the Go-emitted-partial vector.
Verified live: home-page login form → /dashboard with token stashed and
dashboard loading clean; /register form → /dashboard; collections and
devices share the same mechanisms (data-redirect / HX-Refresh). Gates:
make test (9 pkgs incl. both tripwires), vitest 155, Playwright 20,
tsc/vp clean.