Skip to content

fix(a11y): repo dialog and SSO form WCAG 2.2 AA gaps#459

Closed
brandonrc wants to merge 1 commit into
mainfrom
fix/repo-dialog-a11y
Closed

fix(a11y): repo dialog and SSO form WCAG 2.2 AA gaps#459
brandonrc wants to merge 1 commit into
mainfrom
fix/repo-dialog-a11y

Conversation

@brandonrc

Copy link
Copy Markdown
Contributor

Summary

Fixes four WCAG 2.2 AA accessibility gaps in the repository create/edit dialogs and the SSO settings forms (v1.2.0).

  • a11y: repo edit-dialog upstream-auth save has no screen-reader feedback #410 Repo edit dialog now mirrors the upstream-auth save outcome into an in-dialog aria-live region. Success renders role="status" (aria-live="polite"); failure renders role="alert" (aria-live="assertive"). Previously the only feedback was a visual toast, which assistive tech outside the dialog did not reliably announce while focus stayed inside the open dialog. Wired from the parent mutation in repositories-content.tsx.
  • a11y: repo create-dialog form errors not linked via aria-describedby + role=alert #411 Create dialog "key already taken" error now has role="alert", and the key input sets aria-invalid and aria-describedby pointing at the message id, so screen readers announce the error and tie it to the field.
  • a11y: repo edit-dialog auth view↔edit toggle does not manage focus #412 Toggling the upstream-auth view to edit (and back) now moves focus to the first control of the newly-revealed view (the labelled auth-type select in edit mode, the Change/Configure button in view mode) instead of dropping focus on <body>. Implemented with an effect that targets the control by id and defers a frame so the new control exists in the DOM.
  • a11y: WCAG 2.2 AA gap audit — repo dialogs + SSO settings forms #413 Broader gap fixes: required inputs across the repo dialogs and SSO OIDC/LDAP/SAML forms expose aria-required; the edit-dialog auth-type select gains a programmatic <label>; the repository row actions menu trigger gains an accessible name; the edit-key warning is associated via aria-describedby.

Closes #410
Closes #411
Closes #412
Closes #413

Test Checklist

  • Unit tests added/updated
  • E2E Playwright tests added/updated
  • Manually tested locally
  • No regressions in existing tests

New spec e2e/suites/interactions/repositories/repo-dialog-a11y.spec.ts asserts the error/alert + aria-describedby association (#411), focus movement on the auth toggle and the auth-type select label (#412/#413), and the live-region save announcement (#410). Existing unit suites (repo-dialogs.test.tsx 77 tests, repositories page.test.tsx, SSO page.test.tsx) pass unchanged. eslint clean on changed files (one pre-existing unrelated warning); tsc --noEmit reports only pre-existing errors in untouched test files.

UI Changes

  • Playwright E2E spec covers the change
  • Responsive layout verified (mobile + desktop)
  • Dark mode verified
  • Accessibility checked (keyboard navigation, screen reader)
  • N/A - no UI changes

No visual layout changes; additions are ARIA attributes, a focus-management effect, and a status text line in the upstream-auth section of the edit dialog.

Address four accessibility gaps in the repository create/edit dialogs and
SSO settings forms:

- #410: the repo edit dialog now mirrors the upstream-auth save outcome into
  an in-dialog aria-live region (role="status" on success, role="alert" with
  aria-live="assertive" on error) so screen-reader users hear the result. The
  parent mutation previously surfaced only a visual toast.
- #411: the create-dialog duplicate-key error now has role="alert", and the
  key input sets aria-invalid and aria-describedby pointing at the message.
- #412: toggling the upstream-auth view to edit (and back) now moves focus to
  the first control of the newly-revealed view instead of dropping focus on
  the body.
- #413: required inputs across the repo dialogs and SSO OIDC/LDAP/SAML forms
  expose aria-required; the edit-dialog auth-type select gains a programmatic
  label; the repo row actions menu trigger gains an accessible name; the
  edit-key warning is associated via aria-describedby.

Adds a Playwright spec under e2e/suites/interactions/repositories asserting
the error/alert association, focus movement on toggle, and live-region save
announcement.
@brandonrc brandonrc requested a review from a team as a code owner June 2, 2026 16:28
@brandonrc brandonrc added this to the v1.2.0 milestone Jun 2, 2026
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@brandonrc brandonrc left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessibility review (WCAG 2.2 AA). This is the dedicated a11y PR; I verified it against the four linked issues. It is solid work and the targeted fixes are correct. A few gaps remain.

Verified closing #410/#411/#412/#413

  • #411 (create dialog duplicate-key error): correct. #create-key sets aria-invalid and aria-describedby="create-key-error", and the message carries role="alert" with the matching id. SR announces it and ties it to the field (1.3.1, 3.3.1, 4.1.3). Good.
  • #412 (focus on auth view<->edit toggle): the rAF + getElementById(...).focus() effect moves focus to the revealed control and the editAuthModeInitialized guard avoids stealing the dialog's initial focus. Reasonable given SelectTrigger does not forward a ref (2.4.3).
  • #410 (live region for upstream-auth save): correct. role=status/aria-live=polite on success, role=alert/aria-live=assertive on error, and onMutate resets to idle so a repeat success re-announces (4.1.3). Good.
  • #413 (aria-required, auth-type label, actions-menu name): all present. Repo row trigger now has aria-label, the auth-type select has a real <label htmlFor> (4.1.2). Good.

Gaps to consider (non-blocking)

  1. aria-required was added but aria-invalid/error-association was NOT extended to the SSO OIDC/LDAP/SAML forms. Those forms still surface validation only via toast (transient, not programmatically tied to the field). #413 scoped this as a "broader gap" pass, so flagging that required-field validation errors in the SSO forms remain un-associated (3.3.1). Worth a follow-up issue.

  2. #412: focus is moved on toggle, but when the auth-type Select is closed via Escape or the view collapses back to "view" mode, confirm focus lands on #edit-upstream-auth-toggle (the effect covers editAuthMode === "view" so this looks handled) and is not dropped if editRepo.upstream_auth_configured flips the rendered branch. Manual SR pass recommended.

  3. The success live region uses aria-live=polite but the node is always present and only its text changes, which is the correct pattern. However the empty string is rendered as "" inside the div on idle; that is fine. No change needed, just confirming it is not an empty-but-busy region.

No blockers. The three core fixes are correct and verifiable. Not approving (review-only).

@brandonrc brandonrc left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test/E2E quality review (v1.2.0 batch). Verdict: genuine a11y coverage, no theater. This is the model the other admin-gated specs should follow.

#411 duplicate-key alert. Strong. It doesn't just check the error text exists; it asserts the WCAG wiring that was the actual bug: role="alert", aria-invalid="true", and crucially that aria-describedby on the input equals the alert's id (expect(describedBy).toBe(errorId)). A regression that rendered the error text without wiring aria-describedby (the pre-fix state) would fail this. No skip path.

#412 focus management. This is the assertion most specs in this batch lack: await expect(authTypeSelect).toBeFocused() after the toggle. The bug was focus dumped on <body>; this directly distinguishes fixed-vs-broken. The requestAnimationFrame-based focus move in the component is matched by Playwright's auto-retrying toBeFocused, so no arbitrary wait needed. Good.

#410 live region. Honest. It asserts the region has aria-live polite/assertive, then drives the real save and asserts not.toBeEmpty() AND toHaveText(/updated|fail|error/i). The text-regex accepts either success or error, which is the right tolerance for a backend that might reject the dummy token, while still proving the live region announces something (the bug was silence). It does not vacuously pass on an empty region.

No test.skip escape hatches anywhere in this file, no brittle ordering. The component diff backs every selector (#create-key-error, id="edit-upstream-auth-type", label[for=...], data-testid="upstream-auth-status"). Not requesting changes.

knowinglyAnonymous added a commit to knowinglyAnonymous/artifact-keeper-web that referenced this pull request Jun 3, 2026
Addresses review feedback on artifact-keeper#448:
- Programmatically associate the TTL validation error with the input: the
  error <p> now has a stable id ("settings-cache-ttl-error") and role="alert",
  and the input sets aria-describedby to it while invalid. Screen-reader users
  now hear the explanation instead of just "invalid", and the message is
  announced when it appears. Mirrors the reference pattern in artifact-keeper#459.
- Disable the Discard button while a save / cache-TTL mutation is in flight,
  matching the Save button's pending guard, so Discard can't race an
  in-flight save.
- Tests: assert the aria-describedby/role=alert association (and its removal
  when valid again) and the Discard pending guard.

Co-authored-by: Cursor <cursoragent@cursor.com>
@brandonrc

Copy link
Copy Markdown
Contributor Author

Shipped in v1.2.0. The changes from this PR were folded into the release integration branch #466, which was squash-merged to main and tagged as v1.2.0. The code is already on main, so closing this as superseded by #466 rather than merging it (re-applying would just duplicate what already shipped). No work lost. See #466 for the consolidated record.

@brandonrc brandonrc closed this Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant