06 — Screen Specifications (Auth Module)
Production-grade specs for each Auth screen: chrome, states, components, micro-interactions, a11y, responsive, dark/light, keyboard, motion. Tokens:
Sp= 00-shared/02 §3,Ty= typography §2,El= elevation §5,R= radius §4,Mo= motion §6. All components reference 00-shared/03.
1. Login Screen /login
Chrome: no AppBar (brand only); optional app-logo AppAvatar/logo lockup; screen
scrolls with keyboard avoidance (resizeToAvoidBottomInset: true); horizontal padding 16
(phone), 24 (tablet+).
Layout (phone portrait):
Brand lockup [ primaryContainer block, 64h ]
title "Welcome back" headlineMedium
AppTextField email h 56, r-md
AppTextField password h 56, r-md, obscure, autofill
[toggle show/hide password] IconButton 24
Primary CTA "Log in" FilledButton fullWidth h 48
row: "Forgot password?" TextButton
divider "or"
TextButton "Register your school"
Legal microcopy bodySmall onSurfaceVariant
States:
- idle → email/password empty, CTA disabled.
- filled → CTA enabled (no server call until tap).
- submitting → CTA
loadingspinner replaces label, inputs disabled, no double-submit. - error inline (401): error text under field(s)
errorContainerchip + "Invalid email or password." — server doesn't reveal which; copy matches serverauth.service.ts:125. - error locked: banner "Account locked. Try again later." (
auth.service.ts:135). - rate limited 429: inline
AppBanner(error)+ countdownlabelMedium; inputs stay editable but CTA disabled until window; no auto-retry. - offline:
AppOfflineBannertop; CTA disabled with tooltip "Connect to retry". - password manager: autofill hint both snippets (
username,current-password).
Motion: Mo.m-fast ripple; button loading Mo.m-base; error AnimatedSwitcher fade;
screen transition to home Mo.m-base.
A11y: each labeled field, Semantics.error on invalid, error announced via live region;
focus to first invalid on submit; target ≥ 48.
Dark/light: token-only; brand lockup primaryContainer; both pass contrast.
Tablet/desktop: centered card width ≤ 480; skip-to-content; hover states on buttons;
Tab traversal email→password→CTA→forgot.
Safe areas / landscape: all padded within SafeArea; landscape ≤ 640h scrolls.
2. 2FA Challenge Screen (planned)
Layout: same hub; "Enter the 6-digit code from your authenticator"; AppTotpInput 6 boxes
(auto-advance, paste support); CTA Verify; "Use a recovery code" (planned); "Back to login".
Error: pop AppSnackbar error + red ring; auto-focus box 1 on navigation.
3. Register Screen
Header: headlineMedium "Create your school account".
Fields (in order): first name, last name, school/tenant id, email, phone (opt), password
(+ strength hint), confirm. Footer: legal microcopy + "I agree" implied by submit.
CTA "Create account" loading.. on 409 → inline banner under email + focus email.
On success (tokens auto-login, auth.service.ts:95-120): navigate home with "Verify your
email" banner (AppBanner info) + "Resend verification" (rate-limited 5/120s).
4. Verify Email Screen
Centered card; auto submit if ?token= present. States: Skeleton while token introspection
(cannot introspect — client calls verify once), success check icon success, error banner
"Link invalid or expired — resend (X s)" with POST /auth/resend-verification guarded by
rate limit countdown. Deep link token → clipboard capture → auto call.
5 & 6. Forgot / Reset Password
Same spec → see 08_Form_Specifications.md for complete field tables; screens use identical
layout chrome to Login (single card, brand lockup).
7. Security Hub
ListTile rows with Trailing = badge (count or TOTP ON), each icon map:
2FA (Icons.shield), Devices (Icons.devices, count badge = #sessions), API keys
(Icons.key, count). Desktop master-detail reveal. Refresh on return.
8. Sessions/Devices Screen
| Concern | Specification |
|---|---|
| Groups | "This device" pinned first (match on stored device fingerprint/local marker) then others |
| Row | AppListTile leading device icon by platform (phone/tablet/desktop), title deviceName, subtitle "browser · OS · location · ip", trailing AppMenu (Log out this session) |
| Date line | bodySmall "Last active 3h ago · expires in 5d" from lastActivityAt/expiresAt (user-session.schema.ts:36,39) |
| Empty | AppEmptyState icon devices_other, "No other sessions" |
| Loading | AppSkeleton(list) |
| Error | AppErrorState.code UNAUTHENTICATED/INTERNAL |
| Pull | AppRefreshIndicator |
| CTA (bottom, fullWidth) | Log out all devices → AppDialog confirm → POST /auth/logout-all |
| Row action | DELETE /auth/sessions/:id b/ revoke dialog (destructive-style) then optimistic remove + AppSnackbar + UNDO (reenable only via re-login; mark no undo — revoke is irreversible server-side) |
A11y: row semantics button + device + action hint; affordance of confirm menu; dynamic type sizing.
Dark: identical tokens; sessions differ by primaryContainer for current device.
Tablet: two-column (list+detail) master-detail at ≥840px; desktop command key Cmd+Backspace revoke (proposed).
9. API Keys List / Create / Revoke
| Screen | Spec |
|---|---|
| List | Header headlineMedium + subtitle; AppFAB "Add key" (extended, Icons.add); row AppKeyCard (AppListTile): prefix in mono, scopes as AppChips (readonly), lastUsedAt/createdAt line, AppMenu: Revoke. Empty: AppEmptyState |
| Create (bottom sheet, 2/3 height) | AppTextField name (autofocus), scopes AppChips input (optional; free text to CreateApiKeyDto.scopes? string[]), CTA "Create key"; submit loading; on 409/400 banner; success → reveal state: full-key text mono in AppCard + "Copy" button (confirmation Snackbar "Copied") + warning "Save your key — it's shown once" + Done replaces sheet. This card replaces the form; dismiss clears key |
| Revoke | AppDialog "Delete this key?" → DELETE /auth/api-keys/:id → optimistic removal + Snackbar |
10. 2FA Detail Screen
| UI | Spec |
|---|---|
| Off state (default) | AppBanner "Two-factor is off"; TonalButton "Turn on" → POST /auth/2fa/enable → returns {secret, qrCodeUri} |
| Setup stage | QR card (device-render qrCodeUri), secret mono + copy+backup note; "I've saved my secret" CTA |
| Verify stage | 6-digit TOTP input; "Enable" → POST /auth/2fa/verify; 401 → red ring + error; success → status ON with green badge; AppSnackbar "2FA enabled" |
| On state | status row, Disable (destructive text) → enables TOTP input again → POST /auth/2fa/disable (code required by server auth.service.ts:376) |
| Safe area | QR fits safe areas; landscape SingleChildScrollView |
Global dark/light, dynamic type (200%), a11y on all – used tokens are surface,
primaryContainer, outline, onSurfaceVariant; error errorContainer.
Shared micro-interaction & motion notes (all screens)
Mo-m-baseroute transitions;AppBarelevationEl-1; cardsEl-e1.- Snackbar durations: success < 3 s, error ≥ 4s (
00-shared/03 A). - No FAB on pre-auth; FAB appears only ApiKeys list, hide-on-scroll-down.
- Keyboard: default
TextInputAction.next, last field.done;Entersubmits; error fields focus jump. - Safe areas: bottom nav-height respected only in authed; pre-auth uses
MediaQuerypadding.