Skip to content

a11y(4.1.3): Add live-region wrappers for workflow status, event count, filter counts, and Banner#3561

Open
rosanusi wants to merge 3 commits into
mainfrom
wcag/4.1.3-ui-main-live-region-gaps
Open

a11y(4.1.3): Add live-region wrappers for workflow status, event count, filter counts, and Banner#3561
rosanusi wants to merge 3 commits into
mainfrom
wcag/4.1.3-ui-main-live-region-gaps

Conversation

@rosanusi

@rosanusi rosanusi commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes WCAG 2.2 SC 4.1.3 (Status Messages) gaps across ui-main — dynamic surfaces that changed without announcing to screen readers. Verified manually with VoiceOver + Safari/Chrome (see AT notes below).

# Surface Fix
1 WorkflowStatus chip Opt-in announce prop adds role="status" aria-atomic="true"; enabled only on the detail-header chip. Per-row table cells and the status-filter dropdown stay inert — avoids mounting dozens of live regions per list page (no proliferation).
2 Event history polling sr-only role="status" in event-summary-table; debounced (250 ms) so a burst rendered across multiple reactive passes announces a single accurate total ("N new events loaded") instead of per-pass partial counts.
3 Filter result counts Count number + noun wrapped together in one role="status" aria-atomic="true" region on the workflows / activities / schedules pages, so it announces "N Workflows" rather than a context-free "N".
4 Holocene Banner getRole() maps dangerrole="alert" (assertive), defaultrole="status" (polite) + aria-atomic. Banner's type space is only default/danger, so this mapping is complete (not an Alert mismatch).
5 HeartBeat indicator aria-hidden on the decorative EKG wrapper so the animated graphic isn't surfaced as an unlabeled img inside the status chip's aria-atomic live region.

Files changed

  • src/lib/components/workflow-status.svelte, src/lib/layouts/workflow-header.svelte — Defect 1
  • src/lib/components/event/event-summary-table.svelte, src/lib/i18n/locales/en/workflows.ts — Defect 2
  • src/lib/pages/workflows-with-new-search.svelte, src/lib/pages/standalone-activities.svelte, src/lib/pages/schedules.svelte — Defect 3
  • src/lib/holocene/banner/banner.svelte — Defect 4
  • src/lib/components/heart-beat-indicator.svelte — Defect 5

Manual AT verification (VoiceOver + Safari/Chrome)

  • Defect 1 status chip announces on transition; correctly silent on initial page load (initial render, not a change).
  • Defect 2 event count announces an accurate burst total — debounce fix confirmed via DOM instrumentation (one signal = 4 raw events rendered as two +2 passes → single "4 new events loaded").
  • Defect 3 count region verified correct: persistent, inside <main>, never aria-hidden/inert, text mutates in place.
  • Defect 4 Banner role mapping is correct, but dynamic-insertion announcement is not AT-tested: holocene Banner has no consumer in OSS ui (and isn't exported from @temporalio/ui, so it doesn't cascade to cloud-ui). cloud-ui's own banner already uses the same warning → alert, else status pattern. Note: the role sits on a <section> mounted via {#if show} (created with its content), so dynamic appearance may not announce on all SR/browser combos — acceptable for banners shown on load; flagged for awareness.
  • ⚠️ VoiceOver caveat: VO only reliably announces a polite region when the VO cursor is within that region's landmark, and suppresses background polite regions while a menu/dropdown is open. So Defect 3 may be silent on VoiceOver while filtering via an open dropdown — NVDA/JAWS announce regardless. The markup is correct; this is a VO platform limitation, not a defect here.

Test plan

  • Defect 1: trigger a status transition on a running workflow; confirm AT announces the change without navigation
  • Defect 2: open event history for an active workflow, let it poll; confirm AT announces "N new events loaded" per batch (not per row), with accurate counts
  • Defect 3: filter workflow/activity/schedule list; confirm AT announces "N Workflows/Activities/Schedules" on count change (best on NVDA/JAWS; see VO caveat)
  • Defect 4: (not AT-tested — no OSS consumer; role mapping verified by code review; matches cloud-ui's existing banner)
  • Visual regression: all surfaces unchanged visually
  • axe-core: no new violations on workflow detail, workflow list, schedules, activities

Related

  • Follow-up: DT-4199 — applying the first list filter drops focus to <body> (outside <main>), which also blocks the VoiceOver count announcement on that step. Focus-management fix tracked separately.

A11y-Audit-Ref: 4.1.3-ui-main-live-region-gaps

🤖 Generated with Claude Code

…ter counts, and Banner

Defect 1 — WorkflowStatus chip: add role="status" aria-atomic="true" so
status transitions (Running → Completed / Failed / etc.) announce to AT
without requiring navigation.

Defect 2 — Event history polling: track item-count delta in
event-summary-table and emit a sr-only role="status" span announcing
"{N} new events loaded" each time the poll appends rows, avoiding
verbose per-row narration.

Defect 3 — Filter result counts: add role="status" aria-live="polite"
aria-atomic="true" to the workflow, activity, and schedule count spans
so polled count updates announce as status messages.

Defect 4 — Holocene Banner: add getRole() mapping (danger → role="alert",
default → role="status") and aria-atomic="true" on the section element,
mirroring the Alert primitive pattern.

A11y-Audit-Ref: 4.1.3-ui-main-live-region-gaps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
holocene Ready Ready Preview, Comment Jun 26, 2026 7:37pm

Request Review

@github-actions github-actions Bot added a11y Accessibility audit PR a11y:bucket-3 Bucket 3: engineer required a11y:sc-4.1.3 labels Jun 12, 2026
@temporal-cicd

temporal-cicd Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

📊 Strict Mode: 3 errors in 2 files (0.3% of 892 total)

src/lib/components/workflow-status.svelte (2)
  • L106:16: 'count' is possibly 'undefined'.
  • L107:9: 'count' is possibly 'undefined'.
src/lib/holocene/banner/banner.svelte (1)
  • L70:13: Type 'null' is not assignable to type '"search" | "link" | "success" | "error" | "action" | "activity" | "add-square" | "add" | "apple" | "archives" | "arrow-down" | "arrow-left" | "arrow-up" | "arrow-right" | "ascending" | ... 143 more ... | "xmark-square"'.

Generated by 🚫 dangerJS against 9638a85

- WorkflowStatus: gate role="status" behind an opt-in `announce` prop,
  enabled only on the detail-header chip, so per-row table cells and the
  status-filter dropdown no longer mount live regions (no proliferation).
- event-summary-table: debounce the "N new events loaded" announcement so
  a burst rendered across multiple reactive passes announces one accurate
  total instead of per-pass partial counts.
- filter counts (workflows/activities/schedules): wrap the number + noun in
  one role="status" region so it announces "N Workflows", not a bare "N".
- heart-beat-indicator: aria-hidden the decorative EKG wrapper so it isn't
  surfaced as an unlabeled image inside the status chip's live region.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ardiewen ardiewen marked this pull request as ready for review June 26, 2026 18:56
@ardiewen ardiewen requested a review from a team as a code owner June 26, 2026 18:56
Pulls the debounced "N new events loaded" live-region logic out of
event-summary-table into:
- utilities/count-announcer.ts — framework-agnostic debounce/accumulate logic
  (tracks increases, coalesces bursts, clears-then-re-sets so identical
  consecutive counts re-announce); fully unit-tested with fake timers.
- components/live-count-announcer.svelte — thin Svelte adapter that wires a
  reactive count to the announcer and renders the sr-only polite region.

event-summary-table now renders <LiveCountAnnouncer count={items.length} ... />.
Behavior unchanged; the only tricky logic is now isolated and covered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a11y:bucket-3 Bucket 3: engineer required a11y:sc-4.1.3 a11y Accessibility audit PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants