fix: login match design reference

This commit is contained in:
fsylvain committed 2026-09-25 21:28:12 +02:00
1 parent 001f9f4774
commit f1e22f7073
16 files changed
+1183 -57

No files matched your search

@@ -0,0 +1,94 @@
Feature: AuthShell and log-in screen conform to the design spec
As the maintainer of RenewCMS
I want AuthShell and LoginView built from the exact primitives docs/design/components.md
specifies — AppLogo, UPageCard's own title/description styling, the muted page background,
UForm with "Forgot password?" in the Password field's #hint slot, and the neutral-by-default,
red-on-error input border
So that the log-in screen (and every other AuthShell-based screen) matches the visual spec of
record instead of ad hoc markup that happens to look similar
Background:
Given AuthShell is the layout for every public route, per docs/design/routes.md
And AuthShell composes a UPageCard variant="outline" as the centered card, with AppLogo
rendered as the card's first element — left-aligned above the title/description block,
sharing the card's own 24px gap — not as a separate element centered outside/above the
card, per docs/design/components.md's AuthShell section and
docs/design/source/AuthCard.dc.html
And AppLogo is a reusable component (props include size) rendering the brand mark from
docs/design/source/brand/favicon.svg as inline SVG, not an <img>, so it can theme-switch
And UPageCard's title renders through its own title slot as an h1 (tabindex="-1", so route
changes can move focus to it, per AuthShell's a11y spec), and its description renders
through UPageCard's description prop — neither is freeform markup in the default slot
And from the sm breakpoint up, the centered <main> sits on the muted page background
(--ui-bg-muted), the same canvas the rest of the app uses; below sm it sits on --ui-bg
instead, per docs/design/components.md's AuthShell "Responsive" line
And LoginView's form is a UForm wrapping the Username and Password UFormFields and the
submit button
And the Password UFormField's "Forgot password?" link renders through the field's #hint
slot, in the position the Auth board draws it — right-aligned on the label row — per
docs/design/source/RenewCMS Screens Auth.dc.html, group "Log in"
And every UInput on the page uses the neutral control-edge border (--ui-border-accented) by
default, and turns to the error edge (--ui-error) only when its field has an error, per
docs/design/components.md's Input section (":highlight bound to the field error") and
foundations.md's Borders and elevation
Scenario: AppLogo renders the brand mark as inline SVG, and theme-switches
Given a public screen renders inside AuthShell
When AuthShell mounts
Then an AppLogo renders as UPageCard's first element, inside the card, left-aligned, sharing
the card's 24px gap with the title/description block below it — not centered above the card
And the logo is inline SVG sourced from docs/design/source/brand/favicon.svg, at 32px, not as
an <img> tag
And the logo carries alt="RenewCMS" (role="img" aria-label="RenewCMS" on the SVG)
And the logo is not wrapped in a link, since there is nothing public to go back to
And in light mode the black square has no edge stroke
And in dark mode the black square has a 1px edge in --color-chalk-600 (#444444), so it
doesn't disappear into the dark background
Scenario: UPageCard's title is the page h1, and its description uses UPageCard's own styling
Given AuthShell renders with title "Log in" and description
"Welcome back. Log in to manage your articles."
When AuthShell mounts
Then the card is a UPageCard with variant="outline"
And UPageCard's title slot renders an h1 with tabindex="-1" containing "Log in"
And UPageCard's description prop is "Welcome back. Log in to manage your articles."
And neither the title nor the description is rendered as separate freeform markup inside
UPageCard's default slot
Scenario: The page sits on the muted background from sm up, and on the default background below sm
Given AuthShell renders any public screen at a viewport of sm or wider
When AuthShell mounts
Then the centered <main> uses the muted background token (--ui-bg-muted)
And the card keeps its border, is capped at 400px, and its top sits 12vh from the top
Scenario: Below sm, AuthShell drops the muted background, the card border and the card padding
Given AuthShell renders any public screen at a viewport below sm
When AuthShell mounts
Then the centered <main> uses the default background token (--ui-bg), not the muted one
And the card has no border and fills the screen with 20px padding
And the fields and buttons on the page render at the xl size
Scenario: The login form is a UForm
Given the user is on /login
When the login form renders
Then the form is a UForm, not a plain HTML <form>, wrapping the Username UFormField, the
Password UFormField and the submit button
Scenario: "Forgot password?" renders in the Password field's hint slot
Given the user is on /login
When the login form renders
Then the Password UFormField's "Forgot password?" link is rendered through the field's
#hint slot
And the Password field's label reads only "Password", with no link inside it
Scenario: Inputs are neutral by default and turn to the error edge only when their field has an error
Given the user is on /login with no validation errors yet
When the Username and Password UInput fields render
Then both use the neutral control-edge border (--ui-border-accented), with :highlight false
When the user submits with Username "ma" and Password empty (the Validation errors state,
per docs/design/screens/log-in.md)
Then the Username UInput's edge turns to the error color (--ui-error), with :highlight true,
alongside the message "Use at least 3 characters."
And the Password UInput's edge turns to the error color (--ui-error), with :highlight true,
alongside the message "Enter your password."
@@ -0,0 +1,87 @@
# 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 and theme-switches** — mount AuthShell with title "Log in";
assert an inline `<svg>` (not `<img>`) 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 `<a>`; 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 a 1px stroke using
`--color-chalk-600` appears.
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 `<p>` hand-authored in AuthShell's template). This
replaces the older assertion in `AuthShell.spec.ts` that only checked for any `<h1>`; 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 `<main>`'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 `<main>`'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 `<form>` 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; in dark mode the 1px
chalk-600 edge is visible around the square, in light mode it is absent.
- [ ] 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 `<span class="font-semibold">
RenewCMS</span>` (not AppLogo), and its footer is a bare `<span>Log out</span>` 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.