Skip to content

fix(admin): UX/a11y/perf/correctness pass#1249

Merged
iammukeshm merged 3 commits into
developfrom
chore/admin-ux-a11y-perf-pass
May 27, 2026
Merged

fix(admin): UX/a11y/perf/correctness pass#1249
iammukeshm merged 3 commits into
developfrom
chore/admin-ux-a11y-perf-pass

Conversation

@iammukeshm
Copy link
Copy Markdown
Member

What

A UX / accessibility / performance / correctness pass over the operator console (clients/admin), driven by a four-dimension audit — the admin counterpart to the dashboard pass. No backend or src/BuildingBlocks changes.

Why

Admin shipped without eslint-plugin-jsx-a11y (the dashboard has it), so a class of a11y issues went uncaught — plus the lint was red (2 errors) and a public-route Suspense gap could throw on a cold chunk fetch.

Highlights

Build-red + real bugs

  • 2 lint errors fixed: unused grant prop on RowActions; a dangling // eslint-disable react/no-danger referencing an uninstalled rule.
  • Public-route Suspense gap: /login, /forgot-password, /reset-password, /confirm-email are lazy with no Suspense ancestor → wrapped RouterProvider in a top-level boundary.

Accessibility (jsx-a11y now added & passing)

  • Field primitive threads aria-describedby + aria-invalid to its control — one fix makes every RHF form announce hints/errors.
  • notification-bell: removed the focusable aria-hidden click-away, dropped the invalid role="menu", added Escape-to-close, and removed a component-defined-in-render anti-pattern.
  • Skip-to-content link + <main> landmark in AppShell.
  • Accessible names on unlabelled search/filter inputs (users, audits ×3, impersonate) and the icon-only webhook delete.
  • Segmentedrole="group" + aria-pressed; impersonation disclosure → aria-expanded.
  • role="alert" on login/users/tenants inline errors; role="status" on loaders; reduced-motion now stops Tailwind's animate-spin.

Performance

  • Impersonation list: collapsed two overlapping 5s take:200 polls into one fetch + client-side filtering/counts (halves steady-state requests).
  • notification-bell: coalesces the per-event invalidation burst.
  • security: lazy-imports the ~50 KB qrcode lib only at 2FA enrollment (out of the settings chunk).

Correctness

  • Sessions revoke tracks in-flight ids in a Set, so concurrent revokes no longer clear each other's busy state (two files).
  • Invoices row: real <button> inside the <li> instead of a non-interactive <li role="button">.
  • mobile-nav: capture the trigger node inside the effect (ref-in-cleanup footgun).

Tooling

  • Added eslint-plugin-jsx-a11y (recommended) with no-autofocus off and label-has-associated-control depth 3.

Deliberately deferred (follow-ups)

  • Migrating the billing pages off the older Card idiom onto the Console primitives (PageHeader/ErrorBand/EmptyState/Pagination) and converting plan-form to RHF+zod — large, render-equivalent refactor.
  • Replacing the two window.confirm() webhook deletes with a dialog, and gating invoice Void behind confirmation.
  • Mobile horizontal-scroll wrappers on the densest tables (webhooks/audits) — wants visual QA to pick min-widths.

Verification

  • tsc -b clean
  • eslint 0 errors (was 2), 6 pre-existing react-refresh warnings; jsx-a11y active with no violations
  • vite build succeeds
  • Playwright 93/93 pass

🤖 Generated with Claude Code

iammukeshm and others added 3 commits May 27, 2026 11:32
chore(docker): remove redundant root docker-compose.yml

Local dev is covered by Aspire (FSH.Starter.AppHost) and production by
deploy/docker/docker-compose.yml. The root quick-run compose duplicated
infra config with no unique role; nothing references it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
Audit-driven fixes across the operator console. No backend or
BuildingBlocks changes; all verified (tsc clean, eslint 2 errors -> 0,
build OK, Playwright 93/93).

Build-red + real bugs:
- fix 2 lint errors: drop the unused `grant` prop on RowActions
  (active-grants-card) and the dangling `react/no-danger` disable
  directive that referenced an uninstalled rule (security).
- App.tsx: wrap RouterProvider in a top-level Suspense so the public
  lazy routes (login, password reset, confirm-email) have a boundary on
  cold chunk fetch instead of throwing.

Accessibility (admin had no eslint-plugin-jsx-a11y — now added):
- Field primitive now threads aria-describedby + aria-invalid to its
  control, so every RHF form announces hints/errors (one fix, all forms).
- notification-bell: drop the focusable aria-hidden click-away
  (tabIndex=-1), remove the invalid role="menu", add Escape-to-close,
  and stop redefining a component inside render.
- skip-to-content link + <main id> landmark in AppShell.
- accessible names on unlabelled search/filter inputs (users, audits x3,
  impersonate) and the icon-only webhook delete button.
- Segmented toggle gets role="group" + aria-pressed; impersonation
  details disclosure gets aria-expanded.
- role="alert" on login/users/tenants inline errors; role="status" on
  loaders; reduced-motion now also stops Tailwind's animate-spin.

Performance:
- impersonation list: collapse two overlapping 5s take:200 polls into one
  fetch + client-side filtering/counts.
- notification-bell: coalesce the per-event invalidation burst.
- security: lazy-import the ~50KB qrcode lib only at 2FA enrollment.

Correctness:
- sessions revoke: track in-flight ids in a Set so concurrent revokes
  don't clear each other's busy state (user-sessions-card + settings).
- invoices row: real <button> inside the <li> instead of a
  noninteractive <li role="button">.
- mobile-nav: capture the trigger node in the effect (ref-in-cleanup).

Tooling: add eslint-plugin-jsx-a11y (recommended) with no-autofocus off
and label-has-associated-control depth:3; lint passes clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ci: split backend/frontend pipelines, pin SDK, dedupe test load

Replace the monolithic ci.yml with path-scoped backend.yml + frontend.yml so
a client-only change never builds/tests the API and vice versa.

- backend.yml: unit + integration each run ONCE with coverage collection; the
  coverage job now merges those reports instead of re-running the whole solution
  (the old double-run was the bulk of the load). Vuln scan gates on direct
  vulnerable packages. Drops the fragile bin/obj artifact hand-off.
- frontend.yml: lint + tsc/vite build + Playwright E2E, matrixed over admin and
  dashboard (Node 22, npm-cached) — the frontend had no CI before.
- global.json pins the .NET 10 GA SDK; all workflows use global-json-file and
  drop dotnet-quality: preview. Excluded from the template so scaffolded
  consumer projects are unaffected.
- Always-running "Backend CI" / "Frontend CI" gate jobs report green when their
  side is skipped, so required status checks resolve on cross-cutting PRs.

NOTE: branch protection must require the new "Backend CI"/"Frontend CI" checks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
@iammukeshm iammukeshm merged commit a710fd4 into develop May 27, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant