Skip to content

[UX] tt-time-tracker — Error states ​

Draft from /ux-audit on 2026-07-30 (unattended batch run). Not filed. Repo: Dr-Wade/tt-time-tracker · Branch: develop @ bb3238c · Files reviewed: 4 Patterns: none (feature plainly covered by baseline; MCP budget preserved)

Summary ​

tt-time-tracker's 404 (NotFound.vue) is the best error surface seen in this batch and the reference-quality 404 the alignment pass was looking for: a clear illustration, an <h1>, human French copy, and — crucially — two real routes out (context-aware "Retour à l'accueil"/"Se connecter" plus "Page précédente"). It cleanly passes MSG-04, where playout's equivalent fails. Remaining issues are minor accessibility/polish: the page renders in no landmark, the logo alt is redundant, and (project-wide) the route sets no document title and SPA navigation to it is silent to assistive tech.

Note: this feature is the catch-all 404 (routes.ts:71 → NotFound.vue) noted in PROJECT-LEVEL.md; the silent no-forbidden-surface redirect is a separate project-level finding, referenced below, not re-derived here.

Findings ​

1. 404 content sits in no landmark — Medium · A11Y-04 ​

Where: services/client/src/views/NotFound.vue:2; services/client/src/App.vue:2-6What: not-found is a top-level route rendered outside LayoutApp (routes.ts:71), so it does not inherit any page shell. App.vue's root is a plain <div id="app"> with no <main>, and NotFound.vue's outer element is a bare <section> with no accessible name. A <section> is only exposed as a landmark when it has an accessible name, so the entire 404 content — heading and both recovery buttons — lives outside every landmark. The single-<h1> half of A11Y-04 passes. Why it matters: A screen-reader user navigating by landmarks (a primary strategy) finds nothing on this page and cannot jump to its main content or its "way out" buttons; they must read linearly from the top. Fix: Make the wrapper a landmark — change the <section> to <main>, or add role="main"/an aria-label. Cheapest at the App.vue level: wrap the top-level <RouterView> in a <main> so every chrome-less route (login, 404, etc.) is covered at once.

2. Logo image carries a redundant, non-descriptive alt — Low · A11Y-05 ​

Where: services/client/src/views/NotFound.vue:4-8What: The masthead logo uses alt="logo". It is decorative here (the page's meaning is fully carried by the <h1> and copy), so it should be alt="" to be skipped, or carry the product/organisation name if it is meant to be informative. "logo" is neither — a screen reader announces the literal word "logo, image". Why it matters: Small, but it adds a meaningless announcement on a page whose whole job is to orient a lost user quickly. Fix: alt="" (decorative) or the org name (informative). Prefer alt="" here.

3. "Page précédente" can bounce to another dead end — Low · MSG-04 ​

Where: services/client/src/views/NotFound.vue:61-65What: goBack calls router.back() whenever window.history.length > 1. History length only proves some prior entry exists, not that it is a valid in-app page — if the user reached the bad URL by editing another bad URL, or the prior entry was itself a 404, "Page précédente" returns them to a broken/again-404 state (or, if the prior entry is external, leaves the SPA entirely). Why it matters: The secondary escape hatch can loop the user instead of recovering them. The primary "Retour à l'accueil" button is unaffected and always works, which keeps this Low rather than higher. Fix: Acceptable as-is given the reliable primary action; if hardened, gate router.back() on a same-origin/known-route check and fall through to goHome otherwise (the else branch already does the right thing).

Unverified ​

  • A11Y-01 (contrast): Not settled from code. Watch the muted description (text-surface-500 on bg-surface-50, NotFound.vue:26) and the "Page précédente" text button — low-emphasis greys on a near-white surface are the usual sub-4.5:1 offenders. Verify with a contrast tool against the active theme.
  • A11Y-06 (short viewport / mobile keyboard): The centred justify-center layout with a text-[8rem] numeral (:17) is tall; needs a rendered ~700px viewport to confirm nothing clips. No keyboard is involved on this page.
  • PrimeVue Button internals (target size A11Y-02): node_modules is not installed, so the rendered hit area of the text secondary button cannot be read from source. Both buttons carry visible labels, so no A11Y-05 icon-name gap.

Baseline additions ​

  • A11Y-ROUTE-ANNOUNCE (proposed): On client-side navigation the new route must be announced to assistive tech — move focus to the new <h1>/main, or update an aria-live region. Reaching this 404 via an in-app broken link swaps the DOM under App.vue's <Transition> (App.vue:6-21) with no focus move and no live-region update, so a screen-reader user is silently left on "Page introuvable" with no signal the page changed. This is the SPA-navigation analogue of NAV-03 and recurs project-wide; flagging once here.
  • A11Y-ALT-TEXT (proposed): Meaningful images carry descriptive alt; decorative images use alt="". Covers finding 2; overlaps the separately proposed A11Y-07(c) in PROJECT-LEVEL.md — orchestrator to reconcile.

Project-level references (not re-filed) ​

  • NAV-03 — no route sets a document title; index.html:17 is « Tim » everywhere, including this 404. See PROJECT-LEVEL.md.
  • Silent permission redirect / no forbidden surface — non-admins are bounced with no explanation; distinct from this 404 and already in PROJECT-LEVEL.md.
  • CONTENT-01 — not-applicable; no i18n layer by design, see project-level i18n finding.

Cross-project note ​

The A11Y-04 "chrome-less error/auth route sits in no <main>" shape is likely shared: playout (not-found/unauthorized sit outside Layout per PROJECT-LEVEL.md) and customer-portal/members auth+onboarding shells are the exact class already flagged for the <h1> variant of A11Y-04 — check whether those shells provide a <main>. The A11Y-ROUTE-ANNOUNCE gap (SPA route change unannounced) almost certainly affects all four Vue SPAs. Conversely, this 404's MSG-04 pass is worth holding up as the target the other three should copy — none of them currently has a 404 that both explains and links out.