---
phase: 14-auth-pages
plan: "02"
type: execute
wave: 2
depends_on: ["14-01"]
files_modified:
  - backend/templates/auth_login.templ
  - backend/templates/auth_signup.templ
autonomous: false
requirements: [AUTH-UI-01, AUTH-UI-02, AUTH-UI-03]

must_haves:
  truths:
    - "User visits /login and sees gradient background with animated logo icons — no plain white page"
    - "User sees the brand logo above the 'Sign in to Xtablo' heading inside the auth card"
    - "User sees the gsi-material-button Google button above the 'or' divider"
    - "User sees email and password inputs styled by the ui-input design system component"
    - "User sees 'Don't have an account? Sign up' link below the login form"
    - "User visits /signup and sees the same visual treatment with 'Create your account' heading and 'Already have an account? Sign in' link"
    - "HTMX swap behavior is preserved: submitting login form triggers hx-post=/login hx-target=#login-form hx-swap=outerHTML"
    - "All existing auth handler tests pass unchanged"
  artifacts:
    - path: "backend/templates/auth_login.templ"
      provides: "Updated login page using AuthLayout, GoogleButton, ui.FormField/ui.Input, nav link"
      contains: "@AuthLayout"
    - path: "backend/templates/auth_signup.templ"
      provides: "Updated signup page using AuthLayout, GoogleButton, ui.FormField/ui.Input, nav link"
      contains: "@AuthLayout"
  key_links:
    - from: "backend/templates/auth_login.templ LoginPage"
      to: "backend/templates/auth_layout.templ AuthLayout"
      via: "@AuthLayout(\"Sign in to Xtablo\", csrfToken)"
      pattern: "@AuthLayout"
    - from: "backend/templates/auth_login.templ LoginPage"
      to: "backend/templates/auth_components.templ GoogleButton"
      via: "@GoogleButton(providers.Google.StartURL, providers.Google.Configured)"
      pattern: "@GoogleButton"
    - from: "backend/templates/auth_login.templ LoginFormFragment"
      to: "backend/internal/web/ui/form_field.templ"
      via: "@ui.FormField(ui.FormFieldProps{...})"
      pattern: "ui.FormField"
---

## Phase Goal

**As a** user opening the login or signup page, **I want to** see a visually polished auth page with animated background and branded card, **so that** signing in feels like a professional product experience.

<objective>
Migrate auth_login.templ and auth_signup.templ from the old Layout+Card pattern to AuthLayout with the full auth card structure. Replaces raw inputs with @ui.FormField/@ui.Input, wires in GoogleButton and AuthDivider, and adds cross-page navigation links. After this plan, a user visiting /login or /signup sees the complete visual redesign.

Purpose: This is the deliverable vertical slice. Plan 01 delivered the components; this plan connects them to the actual pages users see.
Output: Updated auth_login.templ and auth_signup.templ. Handler signatures unchanged. All existing tests remain green.
</objective>

<execution_context>
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/workflows/execute-plan.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/ROADMAP.md
@.planning/phases/14-auth-pages/14-CONTEXT.md
@.planning/phases/14-auth-pages/14-UI-SPEC.md
@.planning/phases/14-auth-pages/14-PATTERNS.md
@.planning/phases/14-auth-pages/14-01-SUMMARY.md

<interfaces>
<!-- Key types and contracts the executor needs. -->

From backend/templates/auth_login.templ (current — being replaced):
```go
templ LoginPage(form LoginForm, errs LoginErrors, csrfToken string, providers AuthProviderButtons) { ... }
templ LoginFormFragment(form LoginForm, errs LoginErrors, csrfToken string) { ... }
templ AuthProviderButtonsBlock(providers AuthProviderButtons) { ... }
templ AuthProviderButtonControl(provider AuthProviderButton) { ... }
```

Handler-level type (backend/templates/auth_forms.go or equivalent — do not change):
```go
type AuthProviderButtons struct {
    Google AuthProviderButton
}
type AuthProviderButton struct {
    Configured   bool
    StartURL     string
    Label        string
    DisabledLabel string
}
```

From backend/internal/web/ui/form_field.templ:
```go
type FormFieldProps struct {
    Label string
    For   string
    Field templ.Component
    Error string
    Hint  string
}
templ FormField(props FormFieldProps) { ... }
```

From backend/internal/web/ui/input.templ:
```go
type InputProps struct {
    ID          string
    Name        string
    Value       string
    Placeholder string
    Type        string
    Disabled    bool
    Required    bool
    Attrs       templ.Attributes
}
templ Input(props InputProps) { ... }
```

From backend/internal/web/ui/button.templ (already used — keep as-is):
```go
@ui.Button(ui.ButtonProps{
    Label:   "...",
    Variant: ui.ButtonVariantDefault,
    Tone:    ui.ButtonToneSolid,
    Size:    ui.SizeMD,
    Type:    "submit",
})
```

From backend/templates/auth_components.templ (created in Plan 01):
```go
templ AuthLayout(title string, csrfToken string) { ... }
templ GoogleButton(href string, configured bool) { ... }
templ AuthDivider() { ... }
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Migrate auth_login.templ to AuthLayout with ui.FormField inputs</name>
  <files>backend/templates/auth_login.templ</files>
  <read_first>
    - backend/templates/auth_login.templ (current file — read the full file before editing; understand LoginPage, loginCardBody, LoginFormFragment, AuthProviderButtonsBlock, AuthProviderButtonControl)
    - backend/templates/auth_signup.templ (peer file — read to understand the existing AuthProviderButtons usage pattern to keep in mind for Task 2)
    - backend/templates/auth_forms.go (or wherever LoginForm, LoginErrors, AuthProviderButtons types are defined — read to confirm field names)
    - backend/internal/web/ui/form_field.templ (FormFieldProps struct and FormField signature)
    - backend/internal/web/ui/input.templ (InputProps struct and Input signature)
    - .planning/phases/14-auth-pages/14-PATTERNS.md (LoginPage and LoginFormFragment pattern sections for exact field values and structure)
  </read_first>
  <action>
    Replace backend/templates/auth_login.templ entirely. The file must contain exactly these components:

    1. Package declaration: `package templates`
    2. Import: `"backend/internal/web/ui"` (needed for @ui.FormField, @ui.Input, @ui.Button, ui.CSRFField)

    3. `templ LoginPage(form LoginForm, errs LoginErrors, csrfToken string, providers AuthProviderButtons)` — function signature MUST remain identical (handler compatibility).
       Body: `@AuthLayout("Sign in to Xtablo", csrfToken)` with child content:
       - `<div class="auth-card-topbar"></div>` (empty placeholder, no back-link in Phase 14)
       - `<div class="brand-header"><img class="brand-logo" src="/static/logo_dark.png" alt="Xtablo"/></div>`
       - `<div class="title-group"><h1>Sign in to Xtablo</h1></div>`
       - `<div class="auth-body">` containing: `@GoogleButton(providers.Google.StartURL, providers.Google.Configured)`, `@AuthDivider()`, `@LoginFormFragment(form, errs, csrfToken)` — in that order (per UI-SPEC element order contract)

    4. `templ LoginFormFragment(form LoginForm, errs LoginErrors, csrfToken string)`:
       `<form id="login-form" method="POST" action="/login" hx-post="/login" hx-target="#login-form" hx-swap="outerHTML" class="login-form">`
       Inside the form (in order):
       - `@ui.CSRFField(csrfToken)` (preserve exactly)
       - `@GeneralError(errs.General)` (preserve exactly)
       - `@ui.FormField(ui.FormFieldProps{Label: "Email address", For: "email", Field: ui.Input(ui.InputProps{ID: "email", Name: "email", Type: "email", Placeholder: "you@example.com", Value: form.Email, Required: true, Attrs: templ.Attributes{"autocomplete": "email"}}), Error: errs.Email})`
       - `@ui.FormField(ui.FormFieldProps{Label: "Password", For: "password", Field: ui.Input(ui.InputProps{ID: "password", Name: "password", Type: "password", Placeholder: "Your password", Required: true, Attrs: templ.Attributes{"autocomplete": "current-password"}}), Error: errs.Password})`
       - `@ui.Button(ui.ButtonProps{Label: "Sign in to Xtablo", Variant: ui.ButtonVariantDefault, Tone: ui.ButtonToneSolid, Size: ui.SizeMD, Type: "submit"})` (label changes from "Sign in" to "Sign in to Xtablo" per copywriting contract)
       - `<p class="signup-copy">Don't have an account? <a class="signup-link" href="/signup">Sign up</a></p>` (per D-NL01)
       - Close form

       HTMX attributes preserved exactly: `hx-post="/login" hx-target="#login-form" hx-swap="outerHTML"`, form id="login-form".
       The old `class="space-y-5"` on form → `class="login-form"` (auth.css handles layout).
       Old raw `<input>` elements removed — replaced by @ui.FormField + @ui.Input.
       Old `@FieldError(errs.X)` calls removed — errors wired through FormFieldProps.Error field.

    5. REMOVE AuthProviderButtonsBlock and AuthProviderButtonControl components from this file — they are superseded by GoogleButton from auth_components.templ. Verify no other file in backend/templates/ imports or calls these before removing; if called elsewhere, keep the stubs but empty them. (auth_signup.templ also uses AuthProviderButtonsBlock — that file is updated in Task 2 simultaneously.)

    Note: `loginCardBody` helper templ (private, was used by old LoginPage) is no longer needed — remove it.
  </action>
  <verify>
    <automated>
      cd backend && just generate 2>&1 | tail -5 &&
      grep '@AuthLayout' backend/templates/auth_login.templ &&
      grep 'GoogleButton' backend/templates/auth_login.templ &&
      grep 'ui.FormField' backend/templates/auth_login.templ &&
      grep 'signup-copy' backend/templates/auth_login.templ &&
      grep 'hx-post="/login"' backend/templates/auth_login.templ &&
      go test ./templates/... -count=1 2>/dev/null || go test ./... -count=1 -run TestLogin
    </automated>
  </verify>
  <acceptance_criteria>
    - auth_login.templ contains `@AuthLayout("Sign in to Xtablo", csrfToken)`
    - auth_login.templ contains `@GoogleButton(providers.Google.StartURL, providers.Google.Configured)`
    - auth_login.templ contains `@AuthDivider()`
    - auth_login.templ contains `@ui.FormField` (at least 2 occurrences — email and password fields)
    - auth_login.templ contains `class="login-form"` on the form element
    - auth_login.templ contains `hx-post="/login"` and `hx-target="#login-form"` and `hx-swap="outerHTML"` (HTMX preserved)
    - auth_login.templ contains `class="signup-copy"` with href="/signup" link
    - auth_login.templ contains `Sign in to Xtablo` as submit button label
    - auth_login.templ does NOT contain raw `<input type="email"` or `<input type="password"` (replaced by @ui.Input)
    - auth_login.templ does NOT contain `@Layout(` (old layout removed)
    - `just generate` exits 0
    - `cd backend && go test ./... -count=1` all tests pass
  </acceptance_criteria>
  <done>Login page uses AuthLayout with full auth card structure, Google button, FormField inputs, and signup navigation link. HTMX swap behavior preserved. All tests green.</done>
</task>

<task type="auto">
  <name>Task 2: Migrate auth_signup.templ to AuthLayout with ui.FormField inputs</name>
  <files>backend/templates/auth_signup.templ</files>
  <read_first>
    - backend/templates/auth_signup.templ (current file — read before editing)
    - backend/templates/auth_login.templ (just updated in Task 1 — use as pattern reference; signup mirrors login with specific differences noted below)
    - backend/templates/auth_forms.go (SignupForm, SignupErrors types — confirm field names: Email, Password, General)
    - .planning/phases/14-auth-pages/14-PATTERNS.md (SignupPage and SignupFormFragment pattern sections)
  </read_first>
  <action>
    Replace backend/templates/auth_signup.templ entirely. Mirror the login page structure with these differences:

    1. Package declaration: `package templates`
    2. Import: `"backend/internal/web/ui"`

    3. `templ SignupPage(form SignupForm, errs SignupErrors, csrfToken string, providers AuthProviderButtons)` — signature unchanged.
       Body: `@AuthLayout("Create your account", csrfToken)` with child content:
       - `<div class="auth-card-topbar"></div>`
       - `<div class="brand-header"><img class="brand-logo" src="/static/logo_dark.png" alt="Xtablo"/></div>`
       - `<div class="title-group"><h1>Create your account</h1></div>` (different heading)
       - `<div class="auth-body">` containing: `@GoogleButton(providers.Google.StartURL, providers.Google.Configured)`, `@AuthDivider()`, `@SignupFormFragment(form, errs, csrfToken)`

    4. `templ SignupFormFragment(form SignupForm, errs SignupErrors, csrfToken string)`:
       `<form id="signup-form" method="POST" action="/signup" hx-post="/signup" hx-target="#signup-form" hx-swap="outerHTML" class="login-form">`
       Inside the form (in order):
       - `@ui.CSRFField(csrfToken)`
       - `@GeneralError(errs.General)`
       - Email FormField: identical to login (same Label, Placeholder, autocomplete="email")
       - Password FormField: `Label: "Password"`, `For: "password"`, `Placeholder: "12 characters minimum"` (differs from login), `Attrs: templ.Attributes{"autocomplete": "new-password"}` (differs from login — new-password not current-password), `Error: errs.Password`
       - `@ui.Button(ui.ButtonProps{Label: "Create account", ...})` (label differs from login: "Create account" not "Sign in to Xtablo")
       - `<p class="signup-copy">Already have an account? <a class="signup-link" href="/login">Sign in</a></p>` (per D-NL02; different copy and href from login)
       - Close form

       HTMX preserved: `hx-post="/signup" hx-target="#signup-form" hx-swap="outerHTML"`, form id="signup-form".

    5. Remove signupCardBody helper templ (no longer needed).
  </action>
  <verify>
    <automated>
      cd backend && just generate 2>&1 | tail -5 &&
      grep '@AuthLayout' backend/templates/auth_signup.templ &&
      grep 'Create your account' backend/templates/auth_signup.templ &&
      grep 'hx-post="/signup"' backend/templates/auth_signup.templ &&
      grep 'signup-copy' backend/templates/auth_signup.templ &&
      grep 'href="/login"' backend/templates/auth_signup.templ &&
      cd backend && go test ./... -count=1
    </automated>
  </verify>
  <acceptance_criteria>
    - auth_signup.templ contains `@AuthLayout("Create your account", csrfToken)`
    - auth_signup.templ contains `@GoogleButton(providers.Google.StartURL, providers.Google.Configured)`
    - auth_signup.templ contains `@AuthDivider()`
    - auth_signup.templ contains `@ui.FormField` (at least 2 occurrences)
    - auth_signup.templ contains `hx-post="/signup"` and `hx-target="#signup-form"` and `hx-swap="outerHTML"`
    - auth_signup.templ contains `class="signup-copy"` with href="/login" link (pointing to login, not signup)
    - auth_signup.templ contains `12 characters minimum` as password placeholder
    - auth_signup.templ contains `autocomplete": "new-password"` (not current-password)
    - auth_signup.templ contains `Create account` as submit button label
    - auth_signup.templ does NOT contain raw `<input type="email"` or `<input type="password"`
    - auth_signup.templ does NOT contain `@Layout(`
    - `just generate` exits 0
    - `cd backend && go test ./... -count=1` all tests pass (including auth_signup_test.go)
  </acceptance_criteria>
  <done>Signup page uses AuthLayout with full auth card structure, Google button, FormField inputs, and login navigation link. HTMX swap behavior preserved. All tests green.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <what-built>Both auth pages (login and signup) have been migrated to the new auth-card layout. The animated background with 35 logo icons, gradient shell, brand logo, gsi-material-button Google sign-in button, and design-system inputs are all wired in.</what-built>
  <how-to-verify>
    1. Start the backend server: `cd backend && just run` (or `go run ./cmd/web`)
    2. Visit http://localhost:8080/login
       - Confirm: gradient background (purple tones) with animated logos floating across the screen
       - Confirm: centered white/frosted card with brand logo (Xtablo icon) above "Sign in to Xtablo" heading
       - Confirm: Google sign-in button with Google logo icon and "Sign in with Google" text (Material Design style)
       - Confirm: "or" divider with lines on each side
       - Confirm: "Email address" and "Password" inputs using the ui-input design system style (rounded, consistent padding)
       - Confirm: "Sign in to Xtablo" submit button
       - Confirm: "Don't have an account? Sign up" link below the form
    3. Click the "Sign up" link → should navigate to /signup
       - Confirm: same visual treatment (gradient bg, animated logos, auth card)
       - Confirm: "Create your account" heading
       - Confirm: password placeholder reads "12 characters minimum"
       - Confirm: "Already have an account? Sign in" link below the form
    4. Click the "Sign in" link → navigates back to /login
    5. Submit the login form with invalid credentials → HTMX swap replaces the form inline (form stays inside the card, no full page reload, error message appears)
    6. Submit the signup form with mismatched or weak password → HTMX swap replaces the form inline with error
  </how-to-verify>
  <resume-signal>Type "approved" if the visual result matches the above. Or describe any issues found (missing animation, wrong colors, HTMX not swapping, etc.).</resume-signal>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| form input → POST /login | Email and password from user cross the trust boundary into the login handler |
| form input → POST /signup | Email and password from user cross the trust boundary into the signup handler |
| CSRF token | csrf.Token(r) injected by middleware; ui.CSRFField(csrfToken) embeds it in the form |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-14-02-01 | Spoofing | CSRF token in auth forms | mitigate | @ui.CSRFField(csrfToken) preserved in both LoginFormFragment and SignupFormFragment; token validated server-side by gorilla/csrf middleware (Phase 2 wiring, unchanged) |
| T-14-02-02 | Tampering | HTMX hx-post targets | accept | hx-post="/login" and hx-post="/signup" are hardcoded strings in server-rendered templates; not user-controlled; HTMX makes standard POST requests identical to HTML form submission |
| T-14-02-03 | Information Disclosure | Error messages via @GeneralError / FormFieldProps.Error | accept | Error strings are server-controlled (handler generates them); templ auto-escapes all string values preventing XSS; field-level errors communicate only field validity state |
| T-14-02-04 | Elevation of Privilege | GoogleButton href via templ.SafeURL | mitigate | providers.Google.StartURL is constructed server-side from configured OAuth start URL; templ.SafeURL validates the URL before rendering as href attribute; unconfigured state renders disabled button with no href |
| T-14-02-05 | Denial of Service | AnimatedBackground 35 CSS animations | accept | All 35 animations are pure CSS with pointer-events:none; no JS involved; browser handles animation scheduling; no server-side performance impact |
</threat_model>

<verification>
After plan 02 completes:
1. `cd backend && just generate` — exits 0, both auth_login_templ.go and auth_signup_templ.go regenerated
2. `cd backend && go test ./... -count=1` — all tests pass including auth handler tests and auth_signup_test.go
3. Browser: GET /login → gradient + animated bg + auth card + Google button + FormField inputs + nav link
4. Browser: GET /signup → same visual treatment + "Create your account" heading + nav link back to /login
5. Browser: submit invalid login → HTMX swaps form inline (card does not disappear, error appears within card)
</verification>

<success_criteria>
AUTH-UI-01: Login page has gradient background with animated background layer, centered auth card with brand logo, and status banner capability using design tokens — confirmed by browser walkthrough step 2.
AUTH-UI-02: Signup page matches the same visual treatment as login — confirmed by browser walkthrough step 3.
AUTH-UI-03: Google sign-in button uses Material Design gsi-material-button style — confirmed by browser walkthrough (Google icon + "Sign in with Google" text, pill border radius, 40px height).
All auth handler tests pass unchanged (handler signatures not modified).
</success_criteria>

<output>
After completion, create `.planning/phases/14-auth-pages/14-02-SUMMARY.md`
</output>
