Appearance
[UX] members — Error states
Draft from /ux-audit on 2026-07-30 (unattended batch run). Not filed. Repo: bcc-nancy/members · Branch:
develop@2c22f5a· Files reviewed: 3 Patterns: user-feedback/empty-states (navigation/link — not consulted, covered by baseline)
Summary
The admin SPA's only dedicated error surface is the forbidden route (Forbidden.vue), a three-line component that renders a single French BccMessage and nothing else. It states the problem but offers no next step, no recovery action and no heading — where the empty-states pattern's anatomy calls for a message plus a primary action and a secondary recovery path. It is saved from being a chrome-less dead end only because it renders inside Layout, so the sidebar remains a route out. Separately, members has no catch-all 404 route, so an unmatched URL renders an empty content area — this is the cross-project gap already recorded in PROJECT-LEVEL.md ("404 catch-all route: members ✗ missing"), not re-derived here.
Findings
1. Forbidden page tells the user nothing to do next — Medium · MSG-03
Where: admin/src/client/views/Forbidden.vue:1-4What: The entire denied-access surface is one sentence — « Vous n'avez pas les droits nécessaires pour consulter cette page. » It says what went wrong but gives no next step: no "request access from your administrator", no contact, no link back to a page the user can see. The empty-states anatomy (state message → supporting detail → primary action → secondary recovery path) has only its first element here. Why it matters: A non-tech-savvy staff user (the documented audience) who is redirected here after clicking a nav item they lack the capability for is left with a flat refusal and no idea how to proceed or who to ask. Fix: Add a supporting line naming the fix ("Contactez un administrateur pour demander l'accès") and a real action — a RouterLink to home.
2. Error surface offers no in-content route out — Low · MSG-04
Where: admin/src/client/views/Forbidden.vue:1-4What: The view contains zero interactive elements, so the page itself has no recovery affordance. Unlike playout's unauthorized (which sits outside its layout and is a true dead end), this route is a child of Layout (router.ts:66), so the sidebar and header remain and the user is not trapped — which is why this is Low, not High. But the escape depends entirely on ambient chrome; the error content itself never presents an explicit "return home" path the empty-states pattern expects. Why it matters: Recovery relies on the user recognising the sidebar as the way out rather than being offered it; on a narrow viewport the sidebar is behind the mobile Menu pill, making the escape less obvious. Fix: Add a primary action / "Retour à l'accueil" link inside the message block (folds into finding 1's fix).
3. Error state has no heading — Low · A11Y-04
Where: admin/src/client/views/Forbidden.vue:1-4What: The content area for this route is a bare message with no heading element; the empty-states reference markup leads with an <h2> title. A user landing here via redirect gets no page-level heading naming the state ("Accès refusé"). Whether Layout's header supplies any <h1> at all could not be confirmed and is treated as unresolved (see Unverified). Why it matters: Screen-reader users navigating by heading get no landmark for what this page is; the state reads only as loose body text. Fix: Lead the block with a heading ("Accès refusé") at the correct level for the Layout's heading structure.
Unverified
- A11Y-01 (contrast) —
BccMessage severity="error"styling comes from@bcc-code/component-library-vue;node_modulesis not installed, so the error-red text/background contrast cannot be computed. Unverified. - A11Y-06 (responsive / short viewport) — needs a rendered page. Unverified.
- MSG-01 (
role="alert") — whetherBccMessage severity="error"emitsrole="alert"/ a live region is inside the external component library and cannot be read from source here. Unverified. (Note: on this route the message appears on navigation rather than being injected after a user action, so the live-region need is weaker than for inline form errors.) - Heading structure of
Layout— could not confirm whether any<h1>exists in the shell around this route; finding 3 is scoped to the route's own content only.
Baseline additions
none. (Findings 1–3 are covered by existing MSG-03, MSG-04 and A11Y-04; the empty-states "no primary/recovery action" theme is already what MSG-04 encodes.)
Cross-project note
- NAV-03 — the
forbiddenroute sets no distinct document title; fails project-wide, see PROJECT-LEVEL.md (members: static « BCC Nancy Admin »). - No catch-all 404 route — cross-project gap already in PROJECT-LEVEL.md (missing in members and customer-portal; present in playout/tt). Referenced, not re-filed.
- CONTENT-01 — not-applicable; members has no i18n layer (French literals by design), see project-level i18n finding.
- The "denied-access surface exists but is a bare message with no action" shape is worth checking in customer-portal (its
/forbiddenroute) and tt-time-tracker (PROJECT-LEVEL notes tt has no forbidden surface — silent redirect — a worse variant of the same theme). The busy-wait guard atrouter.ts:98that feeds this route is out of scope here (assigned to #8 Auth callback).