Feature: Log in screen As a returning RenewCMS user I want a /login screen that authenticates me against POST /v1/auth/login, shows why a previous attempt or session failed, and offers to create the owner account only while registration is open So that I can get back into the back office from any entry point (a direct visit, an expired session, a completed password reset) with copy that matches what actually happened Background: Given /login is a public route (meta.auth "public") rendered as LoginPage inside AuthShell, per docs/design/routes.md And on mount, LoginPage calls GET /v1/auth/registration to learn whether registration is open And the form has a Username field (placeholder "Your username") and a Password field (a password input with a show/hide toggle, autocomplete "current-password", and a "Forgot password?" link), per docs/design/components.md And client-side validation runs on submit before any request is sent: Username is required with "Use at least 3 characters." under 3 characters, and Password is required with "Enter your password." when empty And a valid submit calls POST /v1/auth/login with { username, password } And on a 200 response, LoginPage re-resolves the session (GET /v1/auth/me, per features/nuxt_ui_shell_foundation.feature and features/api_client_global_error_handling.feature) and then navigates: to the original ?redirect= target if present, else to /home when the session is verified, or to /register/pending when it is not And a 403 response means the username/password pair is wrong, since the API never reveals which field was wrong And a 429 response carries a Retry-After header in seconds, surfaced by useApi and displayed as a live countdown on the submit button; the button re-enables when the countdown reaches zero And a 500 response, or a request that never reaches the server, is surfaced as a network/server error; the fields keep whatever the user typed Scenario: Default — registration is closed Given GET /v1/auth/registration resolves 200 with { "open": false } When the user navigates to /login Then the heading is "Log in" And the description is "Welcome back. Log in to manage your articles." And the Username field shows placeholder "Your username" with no value And the Password field shows no value And the submit button reads "Log in" And no "Create the owner account" footer link is shown Scenario: Fresh install — registration is open Given GET /v1/auth/registration resolves 200 with { "open": true } When the user navigates to /login Then the heading, description and fields render exactly as in the Default state And the footer reads "First time here?" with a "Create the owner account" link Scenario: Validation errors — nothing is sent for a short username and an empty password Given the user is on /login When the user types "ma" into Username, leaves Password empty, and submits Then POST /v1/auth/login is never called And the Username field shows the error "Use at least 3 characters." And the Password field shows the error "Enter your password." Scenario: Submitting — fields and button are disabled while the request is in flight Given the user is on /login with Username "marta" and Password "limewash2026" When the user submits and POST /v1/auth/login has not yet resolved Then the Username and Password fields are disabled And the submit button reads "Logging in…" and shows a loading state Scenario: Wrong credentials — the password is cleared and focused Given the user is on /login with Username "marta" and Password "wrong-password" When the user submits and POST /v1/auth/login resolves 403 Then an error alert reads "That username and password don't match" with description "Check both and try again. Passwords are case-sensitive." And the Username field still shows "marta" And the Password field is cleared and receives focus And the submit button reads "Log in" again, no longer loading Scenario: Too many attempts — the button is disabled with a live countdown Given the user is on /login with Username "marta" and Password "limewash" When the user submits and POST /v1/auth/login resolves 429 with a Retry-After of 42 seconds Then an info alert reads "Too many attempts" with description "For your security, wait a moment before trying again." And the Username field still shows "marta" and the Password field still shows "limewash" And the submit button reads "Try again in 0:42" and is disabled And the Username and Password fields are disabled while the countdown runs, as the board draws them When 42 seconds elapse Then the submit button re-enables and reads "Log in" And the Username and Password fields are editable again Scenario: Session expired — redirected from a 401 elsewhere Given the user is redirected to "/login?redirect=/article/12/edit&reason=expired" When /login renders Then an info alert reads "Your session expired" with description "Log in again to pick up where you left off. Unsaved changes are kept on this device." And the Password field receives focus And the submit button reads "Log in" Scenario: Password changed — arrived from a successful reset Given the user is redirected to "/login?reason=password-changed" When /login renders Then a success alert reads "Password changed" with description "Log in with your new password." And the Username field shows placeholder "Your username" with no value And the submit button reads "Log in" Scenario Outline: Server or network error — the fields keep their values Given the user is on /login with Username "marta" and Password When the user submits and POST /v1/auth/login Then an error alert reads "Couldn't reach RenewCMS" with description "Check your connection and try again." And the Username field still shows "marta" and the Password field still shows And the submit button reads "Log in" again, no longer loading Examples: | password | failure | | "limewash2026" | fails with a 500 response | | "limewash2026" | never reaches the server | | "short1" | fails with a 400 response, because "short1" clears client-side validation (non-empty, 3+ characters) but is under the server's 8-character minimum, which the client does not check | Scenario Outline: A successful log in navigates to the right destination Given the user is on And GET /v1/auth/me, called after the 200 response, resolves 200 with is_verified When the user submits valid credentials and POST /v1/auth/login resolves 200 with a jwt cookie Then the app navigates to Examples: | path | verified | destination | | /login | true | /home | | /login | false | /register/pending | | /login?redirect=/article/12/edit | true | /article/12/edit |