Frontend: A1 — Sign up screen #18

Closed
opened 2026-04-02 11:26:24 +02:00 by marcel · 8 comments
Owner

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)

  • Stacked: brand banner (green-dark bg, logo 28px, app name 22px, tagline) + form below
  • Form padding: 24px 20px

Desktop (> 1024px)

  • Full-viewport 2-column split
  • Left: brand panel ~440px fixed, --green-dark bg
  • Right: form area flex:1, --color-page bg, 48px 56px padding, max-width 380px

Form Fields

  • Name (text input)
  • Email (email input)
  • Password (password input)
  • Submit button (primary green)
  • Login link at bottom ("Already have an account?")

Behavior

  • Submit → POST create user_account → redirect to A2
  • Form validation: email format, password minimum length
  • Login link → navigates to login screen

Acceptance Criteria

  • Mobile: stacked brand banner + form
  • Desktop: 2-column split layout (440px brand | flex:1 form)
  • Form submits to backend signup endpoint
  • Redirects to A2 on success
  • No navigation chrome (tabs, sidebar) visible
## 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) - Stacked: brand banner (green-dark bg, logo 28px, app name 22px, tagline) + form below - Form padding: 24px 20px ### Desktop (> 1024px) - Full-viewport 2-column split - Left: brand panel ~440px fixed, `--green-dark` bg - Right: form area flex:1, `--color-page` bg, 48px 56px padding, max-width 380px ## Form Fields - Name (text input) - Email (email input) - Password (password input) - Submit button (primary green) - Login link at bottom ("Already have an account?") ## Behavior - Submit → POST create `user_account` → redirect to A2 - Form validation: email format, password minimum length - Login link → navigates to login screen ## Acceptance Criteria - [ ] Mobile: stacked brand banner + form - [ ] Desktop: 2-column split layout (440px brand | flex:1 form) - [ ] Form submits to backend signup endpoint - [ ] Redirects to A2 on success - [ ] No navigation chrome (tabs, sidebar) visible
marcel added the kind/featurepriority/high labels 2026-04-02 11:29:50 +02:00
Author
Owner

Spec file: specs/frontend/j6-household-setup.html — screen A1 with mobile + desktop previews, agent table, and LLM implementation guide.

**Spec file:** [`specs/frontend/j6-household-setup.html`](../specs/frontend/j6-household-setup.html) — screen A1 with mobile + desktop previews, agent table, and LLM implementation guide.
Author
Owner

🧑‍💻 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.svelte need 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.ts needs to know the target path for throw redirect(303, '/...'). Clarifying this before implementation avoids a placeholder TODO in 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.svelte and SignupForm.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

  • Use a SvelteKit form action (+page.server.ts actions.default) with use:enhance — not a client-side fetch. This gets progressive enhancement for free and keeps auth logic server-side.
  • The login link at the bottom is a <a href="/login"> — no JS needed, but confirm the login route path before wiring it up.
  • For the password field, consider whether a show/hide toggle is in scope. If it's out of scope for v1, note it explicitly so it doesn't become a last-minute addition.
  • $state() for form field values and a $derived() for isSubmitting (disable button while the action is in flight). No $effect() needed here — form actions handle the redirect.
## 🧑‍💻 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.svelte` need 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.ts` needs to know the target path for `throw redirect(303, '/...')`. Clarifying this before implementation avoids a placeholder `TODO` in 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.svelte` and `SignupForm.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 - Use a SvelteKit form action (`+page.server.ts` `actions.default`) with `use:enhance` — not a client-side `fetch`. This gets progressive enhancement for free and keeps auth logic server-side. - The login link at the bottom is a `<a href="/login">` — no JS needed, but confirm the login route path before wiring it up. - For the password field, consider whether a show/hide toggle is in scope. If it's out of scope for v1, note it explicitly so it doesn't become a last-minute addition. - `$state()` for form field values and a `$derived()` for `isSubmitting` (disable button while the action is in flight). No `$effect()` needed here — form actions handle the redirect.
Author
Owner

🗄️ Backend Engineer — Spring Boot / PostgreSQL

Questions & Observations

  • Endpoint path: The issue says "POST create user_account" but doesn't specify the API path. Is it POST /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 Conflict with a clear error body — not a 500 wrapping a database constraint violation. The email column should have a UNIQUE constraint (ideally citext for case-insensitive uniqueness), and the service layer should catch the constraint violation and surface it as a domain-level 409.

  • What does user_account contain? The form collects Name, Email, Password. Mapping to DB: name varchar, email citext UNIQUE NOT NULL, password_hash varchar NOT NULL. Is name a display name or split into first/last? The spec says "Name (text input)" — confirm this maps to a single name column.

  • 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

  • The POST /api/auth/signup endpoint should be unauthenticated (permit all in SecurityFilterChain), but everything else under /api/** should require authentication.
  • After a successful user_account creation, 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.
  • Make sure the response on success doesn't expose password_hash — use a DTO that returns only id and email (or just a 201 Created with no body if the frontend only needs the redirect).
  • Database constraint: email citext UNIQUE NOT NULL, password_hash varchar(255) NOT NULL, created_at timestamptz NOT NULL DEFAULT now().
## 🗄️ Backend Engineer — Spring Boot / PostgreSQL ### Questions & Observations - **Endpoint path**: The issue says "POST create `user_account`" but doesn't specify the API path. Is it `POST /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 Conflict` with a clear error body — not a 500 wrapping a database constraint violation. The `email` column should have a UNIQUE constraint (ideally `citext` for case-insensitive uniqueness), and the service layer should catch the constraint violation and surface it as a domain-level `409`. - **What does `user_account` contain?** The form collects Name, Email, Password. Mapping to DB: `name varchar`, `email citext UNIQUE NOT NULL`, `password_hash varchar NOT NULL`. Is `name` a display name or split into first/last? The spec says "Name (text input)" — confirm this maps to a single `name` column. - **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 - The `POST /api/auth/signup` endpoint should be unauthenticated (permit all in `SecurityFilterChain`), but everything else under `/api/**` should require authentication. - After a successful `user_account` creation, 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. - Make sure the response on success doesn't expose `password_hash` — use a DTO that returns only `id` and `email` (or just a `201 Created` with no body if the frontend only needs the redirect). - Database constraint: `email citext UNIQUE NOT NULL`, `password_hash varchar(255) NOT NULL`, `created_at timestamptz NOT NULL DEFAULT now()`.
Author
Owner

🧪 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):

  • Empty form submission → validation errors shown, no request fired
  • Invalid email format (e.g. notanemail) → inline error on email field
  • Password below minimum length → inline error on password field
  • Email already registered → appropriate error message displayed (not a 500)
  • Network/server error during submit → user sees an error, not a blank screen
  • Login link navigates to the correct route
  • Submitting twice rapidly (double-click) → doesn't create duplicate accounts (submit button disabled during in-flight request)

Missing acceptance criteria (structural):

  • Tab bar (mobile), inline tab bar (tablet), and sidebar (desktop) are all absent from the DOM — not just hidden via CSS
  • Page <title> and/or <h1> are present and accessible

Suggestions

  • Component tests (Vitest): One test suite for SignupForm.svelte covering: initial render, each validation error state, and the successful submit callback. Query by label text (getByLabelText('Name'), etc.) — never by CSS class.
  • E2E test (Playwright): One focused test for the full happy-path signup flow (fill form → submit → land on A2). Viewport variants: 375px (mobile) and 1280px (desktop). Use a Page Object for the signup page.
  • E2E — no-nav assertion: Add a check that 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.
  • Consider a @parameterized test for the validation rules (email format variants, password length boundaries) to keep the test suite DRY without sacrificing coverage.
## 🧪 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):** - [ ] Empty form submission → validation errors shown, no request fired - [ ] Invalid email format (e.g. `notanemail`) → inline error on email field - [ ] Password below minimum length → inline error on password field - [ ] Email already registered → appropriate error message displayed (not a 500) - [ ] Network/server error during submit → user sees an error, not a blank screen - [ ] Login link navigates to the correct route - [ ] Submitting twice rapidly (double-click) → doesn't create duplicate accounts (submit button disabled during in-flight request) **Missing acceptance criteria (structural):** - [ ] Tab bar (mobile), inline tab bar (tablet), and sidebar (desktop) are all absent from the DOM — not just hidden via CSS - [ ] Page `<title>` and/or `<h1>` are present and accessible ### Suggestions - **Component tests (Vitest)**: One test suite for `SignupForm.svelte` covering: initial render, each validation error state, and the successful submit callback. Query by label text (`getByLabelText('Name')`, etc.) — never by CSS class. - **E2E test (Playwright)**: One focused test for the full happy-path signup flow (fill form → submit → land on A2). Viewport variants: 375px (mobile) and 1280px (desktop). Use a Page Object for the signup page. - **E2E — no-nav assertion**: Add a check that `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. - Consider a `@parameterized` test for the validation rules (email format variants, password length boundaries) to keep the test suite DRY without sacrificing coverage.
Author
Owner

🔒 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.ts and that the action is using the native form action pattern (not a raw fetch POST 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

  • Server-side validation in the Spring controller (@Valid + @RequestBody) must run regardless of client-side validation. Never trust the frontend alone.
  • Ensure the response body on signup failure does not include stack traces, SQL error messages, or internal paths — only a structured error DTO.
  • Log signup events (success and failure) to the audit log, but never log the submitted password, even in error paths.
  • The "Login link" navigates to the login screen — ensure this link does not carry over any state (form values, tokens) from the signup page.
## 🔒 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.ts` and that the action is using the native form action pattern (not a raw `fetch` POST 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 - Server-side validation in the Spring controller (`@Valid` + `@RequestBody`) must run regardless of client-side validation. Never trust the frontend alone. - Ensure the response body on signup failure does not include stack traces, SQL error messages, or internal paths — only a structured error DTO. - Log signup events (success and failure) to the audit log, but **never log the submitted password**, even in error paths. - The "Login link" navigates to the login screen — ensure this link does not carry over any state (form values, tokens) from the signup page.
Author
Owner

🎨 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-dark background with white (--color-page or 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-error on 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

  • The desktop padding spec ("48px 56px") should be clarified: is that padding: 48px 56px (top/bottom 48, left/right 56)? Or 48px vertical + 56px horizontal as separate values? Mapping to --space-* tokens: 48px = --space-12, 56px = --space-14.
  • The max-width: 380px on the form area — confirm this applies to the form card/container, not the entire right column. The right column is flex:1; the form within it is constrained.
  • Accessibility note: the form must have a visible <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.
  • WCAG contrast check: --green-dark text on --color-page background — confirm this passes 4.5:1 for normal text. Should be fine but worth noting in the spec for implementers.
## 🎨 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-dark` background with white (`--color-page` or 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-error` on 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 - The desktop padding spec ("48px 56px") should be clarified: is that `padding: 48px 56px` (top/bottom 48, left/right 56)? Or `48px` vertical + `56px` horizontal as separate values? Mapping to `--space-*` tokens: 48px = `--space-12`, 56px = `--space-14`. - The `max-width: 380px` on the form area — confirm this applies to the form card/container, not the entire right column. The right column is `flex:1`; the form within it is constrained. - Accessibility note: the form must have a visible `<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. - WCAG contrast check: `--green-dark` text on `--color-page` background — confirm this passes 4.5:1 for normal text. Should be fine but worth noting in the spec for implementers.
Author
Owner

🧑‍💻 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/ and src/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/setup is the working path. Final confirmation deferred to #19 — will update the form action once that's settled.

  • Login route path/login (sibling to /signup in 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-error text, 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.ts with use:enhance. Redirect to /household/setup on success.

## 🧑‍💻 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/` and `src/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/setup` is the working path. Final confirmation deferred to #19 — will update the form action once that's settled. - **Login route path** — `/login` (sibling to `/signup` in 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-error` text, 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.ts` with `use:enhance`. Redirect to `/household/setup` on success.
Author
Owner

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
  • /signup added to PUBLIC_ROUTES in auth guard
  • BrandPanel component — green-dark brand column with logo (🥗), app name, tagline, 3 feature icons. Responsive: banner on mobile, 440px column on desktop.
  • SignupForm component — name/email/password fields with inline validation errors (--color-error), password show/hide toggle, native <form method="POST"> for progressive enhancement
  • Signup page — composes BrandPanel + SignupForm in responsive split layout (stacked mobile, 2-column desktop)
  • Server action+page.server.ts POSTs to POST /v1/auth/signup, redirects to /household/setup on success, returns fail(400) on error
  • No-nav-chrome regression test — verifies navigation elements are absent

Commits

  • 66cf538 refactor(auth): make (public) layout bare, move brand panel into login page
  • 56fc7e6 feat(auth): add /signup to public routes
  • e8fe69a feat(auth): add BrandPanel component for signup screen
  • d3a8518 feat(auth): add SignupForm component with validation and password toggle
  • 596652d feat(auth): add signup page with form action
  • bfa8f20 test(auth): add no-nav-chrome regression test for signup page

Branch

feat/issue-16-design-system

## 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 - **`/signup` added to PUBLIC_ROUTES** in auth guard - **`BrandPanel`** component — green-dark brand column with logo (🥗), app name, tagline, 3 feature icons. Responsive: banner on mobile, 440px column on desktop. - **`SignupForm`** component — name/email/password fields with inline validation errors (`--color-error`), password show/hide toggle, native `<form method="POST">` for progressive enhancement - **Signup page** — composes BrandPanel + SignupForm in responsive split layout (stacked mobile, 2-column desktop) - **Server action** — `+page.server.ts` POSTs to `POST /v1/auth/signup`, redirects to `/household/setup` on success, returns `fail(400)` on error - **No-nav-chrome regression test** — verifies navigation elements are absent ### Commits - `66cf538` refactor(auth): make (public) layout bare, move brand panel into login page - `56fc7e6` feat(auth): add /signup to public routes - `e8fe69a` feat(auth): add BrandPanel component for signup screen - `d3a8518` feat(auth): add SignupForm component with validation and password toggle - `596652d` feat(auth): add signup page with form action - `bfa8f20` test(auth): add no-nav-chrome regression test for signup page ### Branch `feat/issue-16-design-system`
Sign in to join this conversation.