Frontend: A1 — Sign up screen #18
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
First screen a new user sees. Creates a
user_account. No navigation chrome — pre-auth layout.Journey: J6 — Household setup (step 1)
Role: New user (unauthenticated)
Layout
Mobile (< 768px)
Desktop (> 1024px)
--green-darkbg--color-pagebg, 48px 56px padding, max-width 380pxForm Fields
Behavior
user_account→ redirect to A2Acceptance Criteria
Spec file:
specs/frontend/j6-household-setup.html— screen A1 with mobile + desktop previews, agent table, and LLM implementation guide.🧑💻 Kai — Frontend Engineer
Questions & Observations
Pre-auth layout suppression: The issue says "no navigation chrome". That means A1 needs a dedicated pre-auth layout (
src/routes/(auth)/+layout.svelte) that renders no<nav>. Is there a(auth)route group already planned, or does the root+layout.svelteneed a conditional? A route group is the clean SvelteKit 2 approach — avoids a boolean prop threading through the root layout."Redirect to A2": What is A2 exactly? Is it another issue/screen in the same series? The form action in
+page.server.tsneeds to know the target path forthrow redirect(303, '/...'). Clarifying this before implementation avoids a placeholderTODOin the action.Password minimum length: The spec says "password minimum length" as a validation rule but doesn't state the value. This needs to be defined before implementation so it can be hardcoded correctly both in client-side UX feedback and in the server-side action.
Component split: Per our architecture rules, this page should be split at minimum into
BrandPanel.svelteandSignupForm.svelte. BrandPanel covers the green column/banner; SignupForm owns all form state and submission. Any objection to that split?Error state display: The acceptance criteria don't mention what happens when validation fails — no visible error messages in the AC checklist. Should inline field errors appear below each input? What's the copy for
email already in use?Suggestions
+page.server.tsactions.default) withuse:enhance— not a client-sidefetch. This gets progressive enhancement for free and keeps auth logic server-side.<a href="/login">— no JS needed, but confirm the login route path before wiring it up.$state()for form field values and a$derived()forisSubmitting(disable button while the action is in flight). No$effect()needed here — form actions handle the redirect.🗄️ Backend Engineer — Spring Boot / PostgreSQL
Questions & Observations
Endpoint path: The issue says "POST create
user_account" but doesn't specify the API path. Is itPOST /api/users,POST /api/auth/signup, or something else? The SvelteKit form action will proxy to the backend, so the path needs to be agreed before implementation.Password minimum length: Validation is mentioned but no minimum is specified. Needs a concrete value (e.g. 8 characters minimum) defined here so backend (
@Size(min = 8)) and frontend validation stay in sync.Duplicate email behavior: What's the contract when a user signs up with an already-registered email? The spec doesn't define this. It should return
409 Conflictwith a clear error body — not a 500 wrapping a database constraint violation. Theemailcolumn should have a UNIQUE constraint (ideallycitextfor case-insensitive uniqueness), and the service layer should catch the constraint violation and surface it as a domain-level409.What does
user_accountcontain? The form collects Name, Email, Password. Mapping to DB:name varchar,email citext UNIQUE NOT NULL,password_hash varchar NOT NULL. Isnamea display name or split into first/last? The spec says "Name (text input)" — confirm this maps to a singlenamecolumn.Who creates the household? The issue is "Household setup (step 1)" but the form only creates a
user_account. Is household creation deferred to A2, or does this endpoint also bootstrap an empty household for the new user?Suggestions
POST /api/auth/signupendpoint should be unauthenticated (permit all inSecurityFilterChain), but everything else under/api/**should require authentication.user_accountcreation, the backend should immediately establish a session (log the user in) so the redirect to A2 lands in an authenticated context. If signup and login are separate operations, there's a gap between them.password_hash— use a DTO that returns onlyidandemail(or just a201 Createdwith no body if the frontend only needs the redirect).email citext UNIQUE NOT NULL,password_hash varchar(255) NOT NULL,created_at timestamptz NOT NULL DEFAULT now().🧪 QA Engineer — Test Coverage
Questions & Observations
The current acceptance criteria cover layout and happy-path submission. They're missing the majority of testable paths. Here's what I'd add:
Missing acceptance criteria (bad paths & edge cases):
notanemail) → inline error on email fieldMissing acceptance criteria (structural):
<title>and/or<h1>are present and accessibleSuggestions
SignupForm.sveltecovering: initial render, each validation error state, and the successful submit callback. Query by label text (getByLabelText('Name'), etc.) — never by CSS class.data-testid="nav"(or whatever selector we use for the nav) is not present in the DOM on this route. This is the kind of regression that's easy to break silently.@parameterizedtest for the validation rules (email format variants, password length boundaries) to keep the test suite DRY without sacrificing coverage.🔒 Sable — Security Engineer
Questions & Observations
Signup is the entry point to the entire authentication system. Getting the design right here prevents a class of vulnerabilities from ever existing.
Critical to clarify before implementation:
CSRF protection: SvelteKit form actions include CSRF protection by default (Origin header check). Confirm this is not disabled in
hooks.server.tsand that the action is using the native form action pattern (not a rawfetchPOST from the client that would bypass it).Session fixation: After a successful signup, the backend must invalidate any pre-existing session and issue a new session ID. Spring Security's session fixation protection handles this if configured (
sessionManagement().sessionFixation().newSession()). Worth explicitly verifying this is in place.Password policy: "Password minimum length" is listed as a validation rule but the minimum is not defined. A minimum of 8 characters is the NIST 800-63B baseline. No maximum should be artificially low (some apps cap at 20 — this breaks passphrase use and is a red flag). I'd suggest min 8, max 128.
Email enumeration: If the error message for a duplicate email says "this email is already registered", an attacker can enumerate valid accounts. Consider a generic "Something went wrong, please try again" for this case — or accept the tradeoff (many apps show it explicitly for UX). Document the decision either way.
Rate limiting: No rate limiting is mentioned. The signup endpoint should be rate-limited to prevent bulk account creation. This is a deployment/middleware concern but should be noted now, not after launch.
Suggestions
@Valid+@RequestBody) must run regardless of client-side validation. Never trust the frontend alone.🎨 Atlas — UI/UX Designer
Questions & Observations
The layout structure is correct and consistent with the design system. A few details need clarification before the spec can be considered implementation-ready:
Brand panel content: The spec lists "logo 28px, app name 22px, tagline" but doesn't define the tagline copy. What does the tagline say? This belongs in the spec so the implementation isn't guessing.
Submit button: Described as "primary green" — confirm this means
--green-darkbackground with white (--color-pageor white) text. This is the standard primary button treatment. The button follows our button rules: 13px, weight 500, tracking 0.04em,--radius-md(6px), font-sans.Login link styling: "Already have an account?" at the bottom — is this a plain text link (
color: --green-dark, no underline by default, underline on hover)? Or a ghost/outline button? Confirm the visual treatment so it's consistent with how we handle secondary text links elsewhere.Error states: No mention of how validation errors are displayed. Per the design system: error text uses
--color-error, appears below the relevant field, 12px font-sans. The field border should also switch to--color-erroron invalid state. This should be in the spec to avoid the implementer making it up.Loading state: No mention of submit button state while the request is in-flight. Should the button show a spinner and be disabled? This is a common UX gap at spec time that becomes a product bug after launch.
Suggestions
padding: 48px 56px(top/bottom 48, left/right 56)? Or48pxvertical +56pxhorizontal as separate values? Mapping to--space-*tokens: 48px =--space-12, 56px =--space-14.max-width: 380pxon the form area — confirm this applies to the form card/container, not the entire right column. The right column isflex:1; the form within it is constrained.<h1>(e.g. "Account erstellen") for screen readers and page structure. The brand panel heading and the form<h1>should not conflict visually — the brand panel text is decorative, the form<h1>is semantic.--green-darktext on--color-pagebackground — confirm this passes 4.5:1 for normal text. Should be fine but worth noting in the spec for implementers.🧑💻 Kai — Frontend Engineer — Implementation Discussion
Worked through all open frontend items. Everything resolved.
Resolved
Route group — Use a
(auth)route group with a bare+layout.svelte(no nav). Structure:src/routes/(auth)/signup/andsrc/routes/(auth)/login/. All future pre-auth screens (forgot password, invite accept, etc.) slot in here. No boolean in the root layout.A2 redirect target —
/household/setupis the working path. Final confirmation deferred to #19 — will update the form action once that's settled.Login route path —
/login(sibling to/signupin the(auth)group).Password minimum length — 8 characters. Used for client-side validation message and must match backend
@Size(min = 8).Error state display — Inline error message below each invalid field.
--color-errortext, 12px font-sans, field border switches to--color-error. No top-of-form banner.Password show/hide toggle — In scope for this issue.
Unresolved
None.
Ready to implement. Component split:
BrandPanel.svelte+SignupForm.svelte. Form action in+page.server.tswithuse:enhance. Redirect to/household/setupon success.Implementation Complete — A1 Sign Up Screen
All acceptance criteria addressed. 110 tests pass, 0 type errors.
What was implemented
(public)layout refactored to bare slot — each pre-auth page now owns its layout/signupadded to PUBLIC_ROUTES in auth guardBrandPanelcomponent — green-dark brand column with logo (🥗), app name, tagline, 3 feature icons. Responsive: banner on mobile, 440px column on desktop.SignupFormcomponent — name/email/password fields with inline validation errors (--color-error), password show/hide toggle, native<form method="POST">for progressive enhancement+page.server.tsPOSTs toPOST /v1/auth/signup, redirects to/household/setupon success, returnsfail(400)on errorCommits
66cf538refactor(auth): make (public) layout bare, move brand panel into login page56fc7e6feat(auth): add /signup to public routese8fe69afeat(auth): add BrandPanel component for signup screend3a8518feat(auth): add SignupForm component with validation and password toggle596652dfeat(auth): add signup page with form actionbfa8f20test(auth): add no-nav-chrome regression test for signup pageBranch
feat/issue-16-design-system