# QA: AuthShell and log-in screen conform to the design spec ## Automated (Vitest + Vue Test Utils, `task test:web`) No `/v1` calls are relevant to these assertions; mount AuthShell and LoginView directly (LoginView already needs `useApi`/`GET /v1/auth/registration` mocked per features/login_screen.qa.md's setup, reused here only as scaffolding, not as the thing under test). 1. **AppLogo renders inline SVG with no edge in either mode** — mount AuthShell with title "Log in"; assert an inline `` (not ``) renders as UPageCard's first child element, left-aligned (not centered) above the title/description block, sharing the card's 24px gap, at 32px, with `role="img" aria-label="RenewCMS"`, and is not inside an ``; assert its outer square has no stroke in light mode; switch to dark mode (toggle the `.dark` class the way `nuxt_ui_shell_foundation`'s color-mode tests do) and assert the outer square still has no stroke, per `docs/design/source/AuthCard.dc.html`'s literal, non-theme-branched `edge: 'transparent'`. 2. **UPageCard title is an h1 with tabindex="-1"** — mount AuthShell with title "Log in" and description "Welcome back. Log in to manage your articles."; assert `wrapper.find('h1').attributes('tabindex')` is `"-1"` and its text is "Log in"; assert the description text "Welcome back. Log in to manage your articles." renders via UPageCard's `description` prop (not as a sibling `

` hand-authored in AuthShell's template). This replaces the older assertion in `AuthShell.spec.ts` that only checked for any `

`; keep that assertion passing (h1 text still equals the title) while adding the tabindex check. 3. **Muted background at sm and up** — mount AuthShell at a `>= 640px` viewport (mock `useBreakpoints`/matchMedia the way other shell tests do); assert `
`'s class list includes the muted background utility (e.g. `bg-muted`); assert the card renders with a border and is capped at 400px width, offset 12vh from the top. 4. **Default background, borderless card, 20px padding, xl controls below sm** — mount AuthShell at a `< 640px` viewport; assert `
`'s class list includes the default background utility (`bg-default`) and not the muted one; assert the card has no border class; assert the card's padding is 20px; assert the Username/Password inputs and submit button render at `size="xl"`. 5. **Login form is a UForm** — mount LoginView at /login; assert the form wrapping the fields is a UForm component instance, not a bare `
` element with only `@submit.prevent` wired by hand. 6. **Forgot password in the #hint slot** — mount LoginView at /login; assert the Password UFormField is passed content (or renders content) in its `hint` slot containing a "Forgot password?" link to /register/reset/request; assert the field's rendered label text is exactly "Password" with no link inside it. 7. **Inputs default to neutral, then highlight red on error** — mount LoginView at /login; assert the Username and Password `UInput` components render with `highlight` falsy (or absent) and the theme's default ring class (`ring-(--ui-border-accented)`); then trigger the Validation errors state (type "ma" into Username, submit with Password empty); assert both `UInput`s now receive `:highlight="true"` (or the field's error is otherwise truthy) so the edge renders in `--ui-error`, alongside "Use at least 3 characters." and "Enter your password." respectively. ### Implementation finding for coder `web/src/views/LoginView.vue`'s Username and Password `UInput`s currently receive no `:highlight` prop at all — `UFormField` gets `:error="usernameError"` / `:error="passwordError"`, but that value is never forwarded to the inner `UInput`. Today the edge never turns red in the Validation errors state; this isn't a stale/inverted binding, it's a missing one. Wiring `:highlight="!!usernameError"` / `:highlight="!!passwordError"` (or equivalent) onto each `UInput` is the fix scenario 7 above pins down. ## Visual (manual verification — desktop and mobile, light and dark) Reference: `docs/design/source/RenewCMS Screens Auth.dc.html`, group "Log in", plus `docs/design/source/RenewCMS Components Product.dc.html`'s AuthShell drawing and `docs/design/source/RenewCMS Components Primitives.dc.html`'s Input drawing. - [ ] AppLogo renders inside UPageCard as its first element, left-aligned above the title (not centered above the card), sharing the card's 24px gap; the mark matches the favicon.svg drawing exactly (white R on a black square), desktop and mobile; no edge stroke is visible around the square in either light or dark mode. - [ ] UPageCard title ("Log in") renders at the card-title size/weight from the AuthShell drawing and receives focus (visible focus ring) on route change; description text matches "Welcome back. Log in to manage your articles." styling, desktop and mobile, light and dark. - [ ] At sm and up: 400px card with its border, 12vh top offset, on the muted page canvas, light and dark. - [ ] Below sm: card fills the screen borderless with 20px padding on the default (not muted) background; fields and the submit button render at the touch (xl, 44px) size, light and dark. - [ ] The Password field's "Forgot password?" link sits right-aligned on the label row (the hint position), matching the Auth board, desktop and mobile, light and dark. - [ ] Username and Password fields show the neutral control-edge border by default; in the Validation errors state (short username, empty password) both fields' edges turn to the error color alongside their messages, in both light and dark. - [ ] Keyboard/focus: Tab order remains Username → Password → show/hide toggle → "Forgot password?" → submit; UPageCard's h1 is reachable/focusable via tabindex="-1" on route change, matching AuthShell's existing a11y spec. ## Manual verification - **AppLogo reused in DashboardShell** — out of scope for this slice (AuthShell/login only). `DashboardShell.vue`'s sidebar header is currently a plain ` RenewCMS` (not AppLogo), and its footer is a bare `Log out` instead of the UserMenu component components.md specifies. Both need their own "Built from" conformance pass in a later DashboardShell slice; AppLogo (built here with a `size` prop) will be ready to drop in at that point without changes.