docs(14): create phase plan for auth pages restyle
Two-wave plan: Plan 01 creates foundation (logo assets, full auth.css replacement with animations, auth_components.templ, auth_layout.templ); Plan 02 migrates auth_login.templ and auth_signup.templ to AuthLayout with @ui.FormField inputs and cross-page nav links, closing AUTH-UI-01..03. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
848f7480a8
commit
5ed9291d76
3 changed files with 628 additions and 1 deletions
|
|
@ -64,6 +64,7 @@ Plans:
|
|||
**Mode:** mvp
|
||||
**Status:** Pending
|
||||
**Requirements:** AUTH-UI-01, AUTH-UI-02, AUTH-UI-03
|
||||
**Plans:** 2 plans
|
||||
**Success Criteria:**
|
||||
1. Login page has gradient background with animated background layer, centered auth card with brand logo, and status banner using design tokens
|
||||
2. Signup page matches the same visual treatment as login
|
||||
|
|
@ -71,7 +72,14 @@ Plans:
|
|||
4. All existing auth handler tests pass unchanged
|
||||
5. Browser walkthrough of login and signup matches the go-backend app.css auth-card design
|
||||
|
||||
**User-in-loop:** Share design references (screenshots from JS app) before this phase begins; /frontend-design skill applies them during plan execution.
|
||||
Plans:
|
||||
**Wave 1**
|
||||
- [ ] 14-01-PLAN.md — Auth foundation: logo assets + auth.css replacement + auth_components.templ + auth_layout.templ
|
||||
|
||||
**Wave 2** *(blocked on Wave 1 completion)*
|
||||
- [ ] 14-02-PLAN.md — Page migration: update auth_login.templ and auth_signup.templ to use AuthLayout + FormField inputs + nav links + browser verify checkpoint
|
||||
|
||||
**User-in-loop:** Browser walkthrough checkpoint in Plan 02 — approve visual result before considering the phase complete.
|
||||
|
||||
### Phase 15: Dashboard & Tablos
|
||||
**Goal:** Restyle the layout shell (sidebar + main) and tablo list/dashboard to match the JS app's sidebar + project-card layout.
|
||||
|
|
|
|||
276
.planning/phases/14-auth-pages/14-01-PLAN.md
Normal file
276
.planning/phases/14-auth-pages/14-01-PLAN.md
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
---
|
||||
phase: 14-auth-pages
|
||||
plan: "01"
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- backend/static/logo_dark.png
|
||||
- backend/static/logo_white.png
|
||||
- backend/internal/web/ui/auth.css
|
||||
- backend/templates/auth_components.templ
|
||||
- backend/templates/auth_layout.templ
|
||||
autonomous: true
|
||||
requirements: [AUTH-UI-01, AUTH-UI-02, AUTH-UI-03]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "logo_dark.png and logo_white.png exist under backend/static/"
|
||||
- "`just generate` completes without error after the new templ files are created"
|
||||
- "auth.css contains all auth card, animated background, gsi-material-button, and animation CSS extracted from go-backend"
|
||||
- "AnimatedBackground templ component emits exactly 35 .background-logo elements each with a single /static/logo_dark.png img"
|
||||
- "GoogleButton templ component renders the gsi-material-button structure with the Google SVG and 'Sign in with Google' label"
|
||||
- "AuthLayout templ component compiles and provides the .login-screen / .card-wrap / .auth-card-shell HTML shell"
|
||||
artifacts:
|
||||
- path: "backend/static/logo_dark.png"
|
||||
provides: "Logo asset for AnimatedBackground and brand header"
|
||||
- path: "backend/internal/web/ui/auth.css"
|
||||
provides: "All auth page CSS: layout, animated background, auth card, Google button, animations"
|
||||
contains: ".gsi-material-button"
|
||||
- path: "backend/templates/auth_components.templ"
|
||||
provides: "AnimatedBackground (35 elements), GoogleButton (gsi-material-button), AuthDivider components"
|
||||
exports: ["AnimatedBackground", "GoogleButton", "AuthDivider"]
|
||||
- path: "backend/templates/auth_layout.templ"
|
||||
provides: "Standalone auth page HTML shell with .login-screen wrapper"
|
||||
exports: ["AuthLayout"]
|
||||
key_links:
|
||||
- from: "backend/templates/auth_layout.templ"
|
||||
to: "backend/templates/auth_components.templ"
|
||||
via: "@AnimatedBackground() call"
|
||||
pattern: "AnimatedBackground"
|
||||
- from: "backend/internal/web/ui/auth.css"
|
||||
to: "backend/tailwind.input.css"
|
||||
via: "@import already present from Phase 13"
|
||||
pattern: "auth.css"
|
||||
---
|
||||
|
||||
## Phase Goal
|
||||
|
||||
**As a** user opening the login 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>
|
||||
Create the auth page foundation: copy logo assets, replace auth.css with full auth card + animation CSS from go-backend, and create the new templ components (AnimatedBackground, GoogleButton, AuthDivider, AuthLayout). These artifacts are consumed by Plan 02 when it migrates the existing login/signup pages.
|
||||
|
||||
Purpose: Plan 01 delivers all new files without touching existing pages. Plan 02 (wave 2) wires them in.
|
||||
Output: logo_dark.png + logo_white.png copied; auth.css replaced (~500 lines); auth_components.templ created (35-element animated bg + Google button + divider); auth_layout.templ created (standalone HTML shell).
|
||||
</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
|
||||
|
||||
<interfaces>
|
||||
<!-- Key types and patterns the executor needs. -->
|
||||
|
||||
From backend/templates/layout.templ (pattern for AuthLayout):
|
||||
```go
|
||||
package templates
|
||||
|
||||
import (
|
||||
"backend/internal/auth"
|
||||
"backend/internal/web/ui"
|
||||
)
|
||||
|
||||
templ Layout(title string, user *auth.User, csrfToken string) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>{ title }</title>
|
||||
<link rel="stylesheet" href="/static/tailwind.css"/>
|
||||
</head>
|
||||
<body ...>
|
||||
{ children... }
|
||||
<script src="/static/htmx.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
```
|
||||
|
||||
AuthLayout signature (from PATTERNS.md):
|
||||
```go
|
||||
templ AuthLayout(title string, csrfToken string) { ... }
|
||||
// No auth.User param — auth pages are always unauthenticated
|
||||
// No sortable.min.js or discussion-sse.js — not needed on auth pages
|
||||
```
|
||||
|
||||
From go-backend/internal/web/views/auth_components.templ (reference — package name changes):
|
||||
- AnimatedBackground: 35 .background-logo elements; single img per element (Phase 14 uses logo_dark.png only, no light-only/dark-only pair)
|
||||
- GoogleButton: accepts (href string, configured bool); <a> when configured, <button disabled> when not
|
||||
- AuthDivider: .divider-row with .divider-line / "or" pill / .divider-line
|
||||
|
||||
go-backend logo paths use /logo_dark.png — backend uses /static/logo_dark.png (note path difference)
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Copy logo assets and replace auth.css</name>
|
||||
<files>backend/static/logo_dark.png, backend/static/logo_white.png, backend/internal/web/ui/auth.css</files>
|
||||
<read_first>
|
||||
- backend/internal/web/ui/auth.css (current 62-line stub being replaced)
|
||||
- go-backend/static/styles.css lines 1062–1510 (auth CSS to extract: .login-screen, .background-layer, .background-logo, .logo-asset, .size-* utilities, .opacity-* utilities, .bg-01 through .bg-35, .card-wrap, .card-glow, .auth-card-shell, .auth-card-topbar, .brand-header, .brand-logo, .title-group, .auth-body, .login-form, .divider-row, .divider-line, .divider-pill, .signup-copy, .signup-link, .status-slot, .status-banner, .status-success, .status-error, .gsi-material-button and all sub-classes)
|
||||
- go-backend/static/styles.css lines 2533–2947 (all @keyframes definitions and .animate-* utility classes)
|
||||
- backend/tailwind.input.css (confirm auth.css import is already present)
|
||||
</read_first>
|
||||
<action>
|
||||
1. Copy binary files verbatim: `cp go-backend/static/logo_dark.png backend/static/logo_dark.png` and `cp go-backend/static/logo_white.png backend/static/logo_white.png`.
|
||||
|
||||
2. Replace backend/internal/web/ui/auth.css entirely. The new file must contain these sections in order (extracted from go-backend/static/styles.css — do NOT copy app-level rules like .app-shell, .dashboard-shell, .sidebar-*, etc.):
|
||||
|
||||
Section A — Page shell (lines ~1062–1079, auth-only portion): `.login-screen` with min-height 100vh, flex center, background var(--gradient-shell), overflow hidden, padding 2rem 1rem, position relative. Do NOT include `.app-shell` rule.
|
||||
|
||||
Section B — Animated background (lines ~1081–1155): `.background-layer`, `.background-logo`, `.logo-asset`, all `.size-06` through `.size-20` utilities (13 classes), all `.opacity-02` through `.opacity-05` utilities (4 classes), all `.bg-01` through `.bg-35` position rules (35 classes).
|
||||
|
||||
Section C — Card wrapper (lines ~1157–1182): `.card-wrap`, `.card-glow`, `.auth-card-shell` (padding: 1.25rem verbatim — not a design token).
|
||||
|
||||
Section D — Card internals (lines ~1184–1425): `.auth-card-topbar`, `.brand-header`, `.brand-logo`, `.title-group`, `.title-group h1` (clamp font-size), `.auth-body`, `.login-form`, `.divider-row`, `.divider-line`, `.divider-pill`, `.signup-copy`, `.signup-link` (including hover state), `.status-slot`, `.status-banner`, `.status-success`, `.status-error`. Skip `.back-home-link`, `.theme-toggle-button`, `.new-experience-link-wrap`, `.forgot-password-row`, `.submit-button`, `.field-stack` (these are go-backend specifics not used in backend's template pattern). Also skip `.field-stack` since inputs use ui-input from Phase 13.
|
||||
|
||||
Section E — Google button (lines ~1427–1509): `.gsi-material-button` and ALL sub-class rules verbatim. Add `display: block; text-decoration: none;` to `.gsi-material-button` to handle the `<a>` variant (go-backend uses `<button>` only, backend uses `<a>` when configured).
|
||||
|
||||
Section F — Keep existing auth-provider rules at bottom for any non-Google controls: `.auth-provider-stack`, `.auth-provider-button`, `.auth-provider-button:hover`, `.auth-provider-button:focus-visible`, `.auth-provider-button-disabled`, `.auth-provider-separator`, `.auth-provider-separator span`, `.auth-provider-separator em` (copy verbatim from the current auth.css stub).
|
||||
|
||||
Section G — Keyframes (lines ~2533–2882): all @keyframes definitions verbatim.
|
||||
|
||||
Section H — Animation utilities (lines ~2883–2947): all .animate-* utility classes verbatim.
|
||||
|
||||
3. No change to backend/tailwind.input.css — the @import "./internal/web/ui/auth.css" line was added in Phase 13 and remains valid.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>
|
||||
ls backend/static/logo_dark.png backend/static/logo_white.png &&
|
||||
grep -c 'gsi-material-button' backend/internal/web/ui/auth.css &&
|
||||
grep -c '@keyframes' backend/internal/web/ui/auth.css &&
|
||||
grep -c 'animate-move-right-slow' backend/internal/web/ui/auth.css &&
|
||||
grep 'login-screen' backend/internal/web/ui/auth.css &&
|
||||
grep 'auth-card-shell' backend/internal/web/ui/auth.css &&
|
||||
grep 'signup-link' backend/internal/web/ui/auth.css
|
||||
</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- backend/static/logo_dark.png exists (binary file, non-zero size)
|
||||
- backend/static/logo_white.png exists (binary file, non-zero size)
|
||||
- auth.css contains `.gsi-material-button` (grep -c returns >= 1)
|
||||
- auth.css contains >= 10 @keyframes definitions (grep -c '@keyframes' returns >= 10)
|
||||
- auth.css contains `.animate-move-right-slow` (at least one animate-* utility)
|
||||
- auth.css contains `.login-screen` rule
|
||||
- auth.css contains `.auth-card-shell` with `padding: 1.25rem`
|
||||
- auth.css contains `.signup-link` rule
|
||||
- auth.css does NOT contain `.app-shell` (wrong rule from go-backend)
|
||||
- auth.css does NOT contain `.dashboard-shell` (wrong rule from go-backend)
|
||||
</acceptance_criteria>
|
||||
<done>Logo assets exist in backend/static/, auth.css is fully replaced with all auth card, animated background, Google button, and animation CSS (~500+ lines total).</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Create auth_components.templ and auth_layout.templ</name>
|
||||
<files>backend/templates/auth_components.templ, backend/templates/auth_layout.templ</files>
|
||||
<read_first>
|
||||
- backend/templates/layout.templ (pattern for HTML shell structure, script tags, head elements)
|
||||
- go-backend/internal/web/views/auth_components.templ (AnimatedBackground 35 elements, GoogleButton structure, AuthDivider — port verbatim with two adaptations: image path /logo_dark.png → /static/logo_dark.png, remove light-only/dark-only pairs)
|
||||
- backend/templates/auth_login.templ (current file — to understand existing AuthProviderButtons type used by GoogleButton)
|
||||
- .planning/phases/14-auth-pages/14-PATTERNS.md (AnimatedBackground element class list: bg-01..bg-35 with outer/inner class assignments per slot)
|
||||
</read_first>
|
||||
<action>
|
||||
1. Create backend/templates/auth_components.templ with package declaration `package templates` and three components:
|
||||
|
||||
AnimatedBackground(): Port all 35 elements from go-backend/internal/web/views/auth_components.templ lines 108–158. Adaptations:
|
||||
- Change all img src from "/logo_dark.png" to "/static/logo_dark.png"
|
||||
- Replace every two-image (light-only/dark-only) pattern with a single img element using just the classes from the light-only image (drop the dark-only img entirely; Phase 14 uses logo_dark.png only per D-AB02 / DEFERRED section)
|
||||
- Parent div carries aria-hidden="true" on .background-layer
|
||||
- Exact class assignments per slot per 14-PATTERNS.md element table (bg-01 through bg-35)
|
||||
|
||||
GoogleButton(href string, configured bool): Renders the Material Design button structure.
|
||||
- When configured=true: `<a class="gsi-material-button" href={ templ.SafeURL(href) }>` wrapping .gsi-material-button-state div, .gsi-material-button-content-wrapper div containing .gsi-material-button-icon div (Google SVG 4 paths), `<span class="gsi-material-button-contents">Sign in with Google</span>`, `<span class="visually-hidden">Sign in with Google</span>`.
|
||||
- When configured=false: `<button type="button" class="gsi-material-button" disabled aria-disabled="true">` with same inner structure.
|
||||
- Google SVG paths (verbatim from go-backend): #EA4335 (path M24 9.5...), #4285F4 (path M46.98 24.55...), #FBBC05 (path M10.53 28.59...), #34A853 (path M24 48...), none fill (path M0 0h48v48H0z).
|
||||
- Label text: "Sign in with Google" (English per D-G03; go-backend uses French — do not copy French text).
|
||||
|
||||
AuthDivider(): Single component, no params.
|
||||
- `<div class="divider-row">` containing `.divider-line` div, `<span class="divider-pill">or</span>`, `.divider-line` div.
|
||||
- "or" in English (not "Ou continuer avec" from go-backend per D-G03 English convention).
|
||||
|
||||
2. Create backend/templates/auth_layout.templ with package declaration `package templates` and signature:
|
||||
`templ AuthLayout(title string, csrfToken string)`
|
||||
- No import of "backend/internal/auth" — auth pages are always unauthenticated (no *auth.User param)
|
||||
- Full HTML shell: `<!DOCTYPE html>`, `<html lang="en">`, head with charset/viewport meta, `<title>{ title }</title>`, `<link rel="stylesheet" href="/static/tailwind.css"/>`
|
||||
- Body: `<div class="login-screen">` containing `@AnimatedBackground()`, `<div class="card-wrap">` containing `<div class="card-glow"></div>` and `<div class="auth-card-shell">{ children... }</div>`
|
||||
- Script: `<script src="/static/htmx.min.js" defer></script>` — auth forms are HTMX-driven. Do NOT include sortable.min.js or discussion-sse.js.
|
||||
- csrfToken parameter is carried through { children... } — the layout itself does not embed a CSRF field; only the form fragments do.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>
|
||||
cd backend && just generate 2>&1 | tail -5 &&
|
||||
grep -c 'background-logo' backend/templates/auth_components.templ &&
|
||||
grep 'Sign in with Google' backend/templates/auth_components.templ &&
|
||||
grep 'AnimatedBackground' backend/templates/auth_layout.templ &&
|
||||
grep 'login-screen' backend/templates/auth_layout.templ
|
||||
</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `just generate` completes with exit code 0 (no templ compile errors)
|
||||
- auth_components.templ contains exactly 35 occurrences of "background-logo" (one per slot)
|
||||
- auth_components.templ contains "Sign in with Google" (English label, D-G03)
|
||||
- auth_components.templ does NOT contain "Continuer avec Google" or "Ou continuer avec" (French text from go-backend)
|
||||
- auth_components.templ contains "/static/logo_dark.png" (not "/logo_dark.png")
|
||||
- auth_components.templ does NOT contain "light-only" or "dark-only" classes (deferred per DEFERRED section)
|
||||
- auth_layout.templ contains `@AnimatedBackground()`
|
||||
- auth_layout.templ contains `login-screen`
|
||||
- auth_layout.templ contains `htmx.min.js`
|
||||
- auth_layout.templ does NOT contain "auth.User" (no user parameter)
|
||||
- auth_layout.templ does NOT contain "sortable.min.js"
|
||||
- `go test ./backend/... -count=1` passes (or `cd backend && go test ./... -count=1`)
|
||||
</acceptance_criteria>
|
||||
<done>auth_components.templ and auth_layout.templ exist in backend/templates/, `just generate` compiles both files without error, and all existing tests remain green.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| Static file serving | logo_dark.png served at /static/logo_dark.png — binary file from go-backend source, no user input |
|
||||
| Template rendering | auth_components.templ and auth_layout.templ render server-controlled static content only |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-14-01-01 | Spoofing | logo_dark.png static file | accept | File is served from backend/static/ with no user-controlled path; binary asset copied from trusted go-backend source |
|
||||
| T-14-01-02 | Tampering | auth.css in tailwind build | accept | CSS is build-time artifact; no runtime user input affects CSS output; tailwind.css is generated once and served as static |
|
||||
| T-14-01-03 | Information Disclosure | AnimatedBackground aria-hidden | mitigate | .background-layer carries aria-hidden="true" per accessibility contract; decorative logos not exposed to screen readers |
|
||||
| T-14-01-04 | Elevation of Privilege | AuthLayout no-auth check | accept | AuthLayout has no auth.User param by design; pages using it are always unauthenticated; handler-level RedirectIfAuthed middleware handles the actual gate |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
After plan 01 completes:
|
||||
1. `ls backend/static/logo_dark.png backend/static/logo_white.png` — both files exist
|
||||
2. `wc -l backend/internal/web/ui/auth.css` — file is > 200 lines (full extraction)
|
||||
3. `grep -c 'background-logo' backend/templates/auth_components.templ` — returns 35
|
||||
4. `grep 'AnimatedBackground' backend/templates/auth_layout.templ` — matches
|
||||
5. `cd backend && just generate` — exits 0
|
||||
6. `cd backend && go test ./... -count=1` — all tests pass (no regressions from new files)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- logo_dark.png and logo_white.png present in backend/static/
|
||||
- auth.css contains full auth card CSS, gsi-material-button CSS, all @keyframes, and all .animate-* utilities (file > 200 lines)
|
||||
- auth_components.templ: AnimatedBackground with exactly 35 background-logo elements using /static/logo_dark.png, GoogleButton with English label, AuthDivider with "or"
|
||||
- auth_layout.templ: standalone HTML shell calling @AnimatedBackground(), no auth.User param
|
||||
- `just generate` exits 0
|
||||
- All existing tests pass unchanged
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/14-auth-pages/14-01-SUMMARY.md`
|
||||
</output>
|
||||
343
.planning/phases/14-auth-pages/14-02-PLAN.md
Normal file
343
.planning/phases/14-auth-pages/14-02-PLAN.md
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
---
|
||||
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>
|
||||
Loading…
Reference in a new issue