Feature: Accept invite screen As a person who opened the link in the invitation email I want /invite/accept?c=CODE to show me the invited address and let me choose a username and password So that I finish setting up my account and arrive logged in Background: Given /invite/accept is a public route (meta.auth "public") rendered as AcceptInvitePage inside AuthShell, per docs/design/routes.md And on mount AcceptInvitePage reads the "c" query parameter once: if it is absent or empty no request is made and the "Invalid or used link (404)" state renders And otherwise it calls GET /v1/auth/invite?c=CODE and shows "Checking the link" until it resolves And the Username and Password rules are Register's, including PasswordStrengthHint, with no confirm field; validation runs on blur and on submit and nothing is sent when it fails And a submit that passes calls POST /v1/auth/invite/accept with { code: c, username, password } And a logged-in visitor sees the same screen: the route stays public and nothing redirects And the board is docs/design/source/RenewCMS Screens Auth.dc.html, group "Accept invite"; the board frame decides layout and sample data (lea.martin@renew.example, "lea", "limewash2026", "marta") Scenario: Checking the link When the user opens /invite/accept?c=abc123 and GET /v1/auth/invite has not resolved Then a full-card spinner reads "Checking your invite…" Scenario: Default Given GET /v1/auth/invite?c=abc123 resolves 200 with {"email": "lea.martin@renew.example"} Then the heading is "Accept your invite" And the description is "Choose a username and password to finish setting up your account." And the Email field has placeholder "you@example.com", shows "lea.martin@renew.example", is read-only, with help "From your invitation." And the Username field has placeholder "e.g. lea", counter "0 / 20" and help "Letters and numbers, 3 to 20 characters. You'll use it to log in." And the Password field is a password input with show/hide and checklist "At least 8 characters" unmet And the submit button reads "Accept invite" And the footer reads "Already have an account?" with a "Log in" link to /login Scenario: Validation errors Given the form is shown, Username is "ma" and Password is "lime" When the user blurs both fields and submits Then POST /v1/auth/invite/accept is never called And the Username shows counter "2 / 20" in error and the error "Use 3 to 20 characters." And the checklist item "At least 8 characters" is in its error state Scenario: Filled, valid Given Username is "lea" and Password is "limewash2026" with the password shown Then the counter reads "3 / 20", the checklist item "At least 8 characters" is met and no error shows Scenario: Submitting When the user submits valid values and POST /v1/auth/invite/accept has not resolved Then the fields and button are disabled And the submit button reads "Creating account…" and is loading Scenario: Username taken (409) When POST /v1/auth/invite/accept resolves 409 with body "That username is taken. Try another." Then the Username keeps "marta", shows counter "5 / 20", is focused and shows the error "That username is taken. Try another." And the Password keeps its value, the form re-enables and the button reads "Accept invite" Scenario Outline: The link does not work When Then no form is shown And a ResultPanel (tone error, icon i-lucide-link-2-off) reads "This invite link doesn't work" with text "It may be incomplete, or it may already have been used. If you already set up your account, log in instead." and note "Open the link from your email again, or copy the full address into your browser." And its only action is a primary "Go to log in" that navigates to /login Examples: | trigger | | the user opens /invite/accept with no c parameter (no request is made) | | GET /v1/auth/invite?c=abc123 resolves 404 | | the submit resolves 404, replacing the form | | the submit resolves 409 with apperr.EmailHasAccountErrorMessage | Scenario Outline: Expired link (410) When Then no form is shown And a ResultPanel (tone error, icon i-lucide-clock) reads "This invite has expired" with text "Invites work once, for 3 days. Ask the person who invited you to send a new one." And its only action is a secondary "Go to log in" that navigates to /login Examples: | trigger | | GET /v1/auth/invite?c=abc123 resolves 410 | | the submit resolves 410, replacing the form | Scenario Outline: Server error When the submit Then an error Alert (icon i-lucide-circle-x) reads "Couldn't create the account" with "Something went wrong on the server. Try again in a moment." above the heading And the form is still shown with Email, Username "lea" and Password "limewash2026" kept, re-enabled Examples: | failure | | resolves 500 | | resolves 429 | | never reaches the server | Scenario: Success When POST /v1/auth/invite/accept resolves 200 with the jwt cookie Then a ResultPanel (tone success, icon i-lucide-circle-check) reads "Your account is ready" with text "Taking you to your dashboard…" And a Toast (icon i-lucide-circle-check) reads "Welcome to RenewCMS" with "You're logged in as lea." And after 800 ms the app navigates to /home with no verification step