---
phase: 02-authentication
plan: 04
subsystem: auth
tags: [go, htmx, templ, signup, argon2id, sessions, postgres, sqlc, chi]

# Dependency graph
requires:
  - phase: 02-authentication/01
    provides: "migrations + sqlc InsertUser/GetUserByEmail"
  - phase: 02-authentication/02
    provides: "auth.Hash, auth.Verify, DefaultParams, TestParams"
  - phase: 02-authentication/03
    provides: "auth.Store.Create, SetSessionCookie, ResolveSession, RedirectIfAuthed"
provides:
  - "GET /signup renders SignupPage with email+password form"
  - "POST /signup: validate -> argon2id hash -> InsertUser -> Store.Create -> set cookie -> 303 redirect"
  - "POST /signup HTMX path: 200 + HX-Redirect: /"
  - "POST /signup validation errors render inline (HTMX) or full page (no-JS)"
  - "Duplicate email detected via pgconn.PgError code 23505"
  - "Authed users GET /signup -> 303 Location: / (RedirectIfAuthed)"
  - "ResolveSession wired into chi middleware stack (D-24 order)"
  - "SignupForm + SignupErrors types in templates package"
  - "FieldError + GeneralError templ error primitives"
affects: [02-05-login, 02-06-protected-routes, 02-07-csrf]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "SignupForm/SignupErrors defined in templates package (not handlers) to avoid import cycle between templates and internal/web"
    - "Test helper (setupTestDB) duplicated into web package test file; no cross-package import of _test.go files"
    - "Pre-seed test users via InsertUser + auth.TestParams (not handler POST) to avoid 250ms argon2 DefaultParams cost in setup"
    - "nil-Store guard in ResolveSession for Phase 1 unit-test compatibility with AuthDeps{} zero value"
    - "renderSignupError helper: HTMX path renders fragment, non-HTMX renders full page (D-19)"

key-files:
  created:
    - backend/templates/auth_forms.go
    - backend/templates/auth_form_errors.templ
    - backend/templates/auth_signup.templ
    - backend/templates/auth_signup_test.go
    - backend/internal/web/handlers_auth.go
    - backend/internal/web/handlers_auth_test.go
    - backend/internal/web/testdb_test.go
  modified:
    - backend/internal/web/router.go
    - backend/internal/web/handlers_test.go
    - backend/cmd/web/main.go
    - backend/internal/auth/middleware.go

key-decisions:
  - "SignupForm/SignupErrors defined in templates package (auth_forms.go) — avoids import cycle; handlers import templates package directly"
  - "setupTestDB duplicated into web package test file (~100 LOC) — Go does not allow importing _test.go files across packages; duplication chosen over moving infra into production code"
  - "POST /signup outside RedirectIfAuthed group — GET guard handles common case; authed POST gets a response rather than silent 303"
  - "nil-Store guard added to auth.ResolveSession — enables Phase 1 unit tests to pass AuthDeps{} zero value without panic"

patterns-established:
  - "Pattern: templ form types in templates package sibling .go file"
  - "Pattern: renderXxxError helper selects fragment vs full page by HX-Request header"
  - "Pattern: test pre-seed with TestParams hash via direct sqlc call (not handler POST)"

requirements-completed: [AUTH-01, AUTH-03, AUTH-05]

# Metrics
duration: 25min
completed: 2026-05-14
---

# Phase 2 Plan 04: Signup Vertical Slice Summary

**Signup flow end-to-end: templ form + POST handler + argon2id hash + InsertUser + Store.Create + session cookie + 303 redirect wired into chi with ResolveSession (D-24)**

## Performance

- **Duration:** ~25 min
- **Started:** 2026-05-14T22:16:00Z
- **Completed:** 2026-05-14T22:18:30Z
- **Tasks:** 2
- **Files modified:** 11

## Accomplishments

- Signup form (full page + HTMX fragment) renders with email/password fields; email round-trips; password never echoed
- POST /signup handler validates input, hashes with argon2id DefaultParams, inserts user, creates session, sets cookie, redirects
- HTMX fast-path returns 200 + HX-Redirect:/; non-JS path returns 303 See Other
- Duplicate email (pgconn 23505) renders "already in use" inline; no second row inserted
- Authed users hitting GET /signup bounce to / (RedirectIfAuthed)
- ResolveSession wired at position 5 in middleware stack per D-24
- 8 integration tests all pass against real Postgres; Phase 1 unit tests unaffected

## Task Commits

1. **Task 1: Signup templates + render smoke tests** - `73935ed` (feat)
2. **Task 2: SignupPostHandler + router wiring + integration tests** - `efdc16b` (feat)

## Files Created/Modified

- `backend/templates/auth_forms.go` - SignupForm and SignupErrors types (in templates pkg to avoid import cycle)
- `backend/templates/auth_form_errors.templ` - FieldError and GeneralError templ primitives
- `backend/templates/auth_signup.templ` - SignupPage (full) + SignupFormFragment (HTMX swap target)
- `backend/templates/auth_signup_test.go` - 3 render smoke tests (form renders, error renders, password not echoed)
- `backend/internal/web/handlers_auth.go` - SignupPageHandler, SignupPostHandler, AuthDeps, renderSignupError
- `backend/internal/web/handlers_auth_test.go` - 8 TestSignup_* integration tests
- `backend/internal/web/testdb_test.go` - setupTestDB helper (duplicated from auth package)
- `backend/internal/web/router.go` - NewRouter updated: accepts AuthDeps, mounts ResolveSession + signup routes
- `backend/internal/web/handlers_test.go` - Pass AuthDeps{} to NewRouter (Phase 1 tests updated)
- `backend/cmd/web/main.go` - Build AuthDeps (sqlc.Queries + auth.Store + secure flag), pass to NewRouter
- `backend/internal/auth/middleware.go` - nil-Store guard in ResolveSession for Phase 1 test compatibility

## Decisions Made

**Where SignupForm/SignupErrors live:** Templates package (`backend/templates/auth_forms.go`). The handler file imports the templates package; defining types in templates avoids a circular dependency (`templates` importing `internal/web`). Handlers reference these types as `templates.SignupForm`, `templates.SignupErrors`.

**Test helper strategy:** Duplicated `setupTestDB` (~100 LOC) into `backend/internal/web/testdb_test.go`. Go does not allow importing `_test.go` files across packages. The duplication is minimal and keeps test infra out of production code paths. Alternative (shared `internal/testhelpers` package) would put infra in a non-test file.

**POST /signup outside RedirectIfAuthed group:** The GET /signup route is inside the `RedirectIfAuthed` group. POST /signup is intentionally outside because an authed user directly submitting the form should get a proper error response rather than a silent 303 that could confuse tooling.

**nil-Store guard in ResolveSession:** Phase 1 unit tests in `handlers_test.go` call `NewRouter(stubPinger{}, "./static", AuthDeps{})` with a zero-value `AuthDeps` (nil `Store`). Without the nil guard, ResolveSession would panic on cookie lookup. The guard is a no-op pass-through when `Store == nil`.

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 2 - Missing Critical] nil-Store guard added to auth.ResolveSession**
- **Found during:** Task 2 (updating handlers_test.go to pass AuthDeps{})
- **Issue:** Phase 1 tests pass `AuthDeps{}` zero value; ResolveSession would panic on `store.Lookup()` call if any cookie was present (or if future tests hit that path)
- **Fix:** Added `if store == nil { next.ServeHTTP(w, r); return }` at the top of the returned handler closure
- **Files modified:** backend/internal/auth/middleware.go
- **Verification:** Phase 1 tests pass with `AuthDeps{}` zero value; all Phase 2 signup tests pass with real store
- **Committed in:** efdc16b

---

**Total deviations:** 1 auto-fixed (Rule 2 — missing critical nil guard)
**Impact on plan:** Essential for backward compatibility with Phase 1 unit tests. No scope creep.

## Issues Encountered

None — plan executed cleanly.

## Known Stubs

None — the signup form is fully wired end-to-end with real DB operations.

## Next Phase Readiness

- Plan 05 (login handler) can now build on the same `AuthDeps` pattern and templ primitives
- `SignupFormFragment` and `FieldError`/`GeneralError` primitives are ready for reuse in the login form
- `ResolveSession` is live in the middleware stack; `RequireAuth` is implemented (Plan 03) and ready for Plan 06 protected routes
- `auth.TestParams` + direct `InsertUser` pre-seeding pattern is established for future tests

---
*Phase: 02-authentication*
*Completed: 2026-05-14*
