Skip to content

[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_modules is 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") — whether BccMessage severity="error" emits role="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 forbidden route 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 /forbidden route) 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 at router.ts:98 that feeds this route is out of scope here (assigned to #8 Auth callback).