Skip to content

a11y(4.1.2): require label on CodeBlock primitive; add aria-readonly for read-only mode#3557

Merged
ardiewen merged 4 commits into
mainfrom
wcag/4.1.2-codemirror-no-name
Jul 13, 2026
Merged

a11y(4.1.2): require label on CodeBlock primitive; add aria-readonly for read-only mode#3557
ardiewen merged 4 commits into
mainfrom
wcag/4.1.2-codemirror-no-name

Conversation

@rosanusi

Copy link
Copy Markdown
Contributor

Summary

  • Makes label: string required on the CodeBlock primitive (src/lib/holocene/code-block.svelte), applied to CodeMirror's .cm-content via EditorView.contentAttributes.of({ 'aria-label': label }) — every CodeMirror instance now has an accessible name
  • Adds aria-readonly="true" to contentAttributes when editable=false so AT (especially JAWS forms-mode) correctly conveys non-editability instead of announcing the element as an editable text field
  • Adds a dev-time onMount guard that throws (dev) / console.errors (prod) when label is empty at mount, catching dynamic-spread cases that TypeScript can't reach
  • Fixes the orphaned <Label for={id}> association in PayloadInput (§NEW1): replaces <Label for> with <span> and forwards label to the inner <CodeBlock> so the accessible name lands on the labelable .cm-content element
  • Migrates all ~28 affected files: primitive + 4 domain wrappers + ~22 direct consumers

Affected files

Primitive + wrappers (5 files):

  • src/lib/holocene/code-block.svelte
  • src/lib/components/payload/payload-code-block.svelte
  • src/lib/components/payload-input.svelte
  • src/lib/components/payload-input-with-encoding.svelte
  • src/lib/components/workflow/input-and-results-payload.svelte

Locale additions (5 files):

  • src/lib/i18n/locales/en/common.tsjson
  • src/lib/i18n/locales/en/events.tsimport-format-1, import-format-2
  • src/lib/i18n/locales/en/standalone-activities.tsactivity-input, activity-outcome, cluster-config, namespace-config
  • src/lib/i18n/locales/en/workers.tsheartbeat-config, setup-guide-command
  • src/lib/i18n/locales/en/workflows.tsquery-result, callback-metadata, update-input, update-result

Direct consumers (~23 files): event-card, workflow-error-stack-trace, schedules-calendar-view, schedule-frequency-panel, activity-input-and-outcome, standalone-activities-disabled, serverless-worker-setup-guide, worker-heartbeats-disabled, update-confirmation-modal, pending-activities, pending-activity-card, pending-nexus-operation-card, workflow-callback, workflow-json-navigator, import-events, standalone-activity-details, standalone-activity-search-attributes, workflow-call-stack, workflow-memo, workflow-metadata, workflow-query, workflow-search-attributes, archival/+page

Test plan

  • Screen reader: navigate to Workflow History Input/Result panels — .cm-content announces correct label; JAWS does not auto-switch to forms mode on read-only blocks
  • Screen reader: navigate to Workflow Queries query-arg input (editable) — announces label and allows typing; aria-readonly absent
  • Dev-mode enforcement: render <CodeBlock content="…" label="" /> — confirm it throws; add non-empty label — confirm no throw
  • Visual regression: all updated surfaces render identically to before; no visible change
  • pnpm check passes (no new type errors beyond pre-existing $$_$$ slot interop errors on main)
  • PayloadInput standalone (e.g. update-confirmation-modal): visible <span> label text renders correctly; CodeBlock has aria-label

A11y-Audit-Ref: 4.1.2-codemirror-no-name

🤖 Generated with Claude Code

@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 Jul 13, 2026 5:31pm

Request Review

<div class="flex flex-col gap-2">
<h5>Input</h5>
<PayloadCodeBlock value={input} />
<PayloadCodeBlock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Type 'IPayloads | undefined' is not assignable to type 'object | IPayloads | IPayload'.

</p>
{#key activity.attempt}
<PayloadCodeBlock value={activity.heartbeatDetails} maxHeight={384} />
<PayloadCodeBlock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Type 'IPayloads | null | undefined' is not assignable to type 'object | IPayloads | IPayload'.

<div class="flex flex-col gap-2">
{#if searchAttributes}
<PayloadCodeBlock value={searchAttributes.indexedFields} />
<PayloadCodeBlock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Type 'Record<string, IPayload> | undefined' is not assignable to type 'object | IPayloads | IPayload'.

{translate('events.attribute-group.search-attributes')}
</h3>
<PayloadCodeBlock value={workflow.searchAttributes.indexedFields} />
<PayloadCodeBlock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Type 'Record<string, string> | undefined' is not assignable to type 'object | IPayloads | IPayload'.

label={translate('workflows.query-result')}
copyIconTitle={translate('common.copy-icon-title')}
copySuccessIconTitle={translate('common.copy-success-icon-title')}
testId="query-result"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Type 'false | "opacity-50"' is not assignable to type 'string | undefined'.

</h3>
{#if workflow?.searchAttributes}
<PayloadCodeBlock value={workflow.searchAttributes.indexedFields} />
<PayloadCodeBlock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Type 'Record<string, string> | undefined' is not assignable to type 'object | IPayloads | IPayload'.

@temporal-cicd

temporal-cicd Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

📊 Strict Mode: 64 errors in 16 files (7.2% of 895 total)

src/lib/holocene/code-block.svelte (6)
  • L152:32: 'editorView.contentHeight' is possibly 'undefined'.
  • L172:24: Type 'number | boolean' is not assignable to type 'boolean'.
  • L184:23: Type 'number | undefined' is not assignable to type 'number'.
  • L184:34: Type 'number | undefined' is not assignable to type 'number'.
  • L197:8: 'editorView' is possibly 'undefined'.
  • L296:30: Type 'boolean | 0' is not assignable to type 'boolean | undefined'.
src/lib/components/payload/payload-code-block.svelte (1)
  • L77:43: 'data.payloads' is possibly 'null' or 'undefined'.
src/lib/components/workflow/client-actions/update-confirmation-modal.svelte (4)
  • L41:38: Property 'failure' does not exist on type 'IOutcome | null | undefined'.
  • L44:38: Property 'success' does not exist on type 'IOutcome | null | undefined'.
  • L92:6: Type 'string | undefined' is not assignable to type 'string'.
  • L121:9: 'updateDefinitions.length' is possibly 'undefined'.
src/lib/components/event/event-card.svelte (6)
  • L162:19: Parameter 'key' implicitly has an 'any' type.
  • L162:24: Parameter 'value' implicitly has an 'any' type.
  • L212:15: Parameter 'key' implicitly has an 'any' type.
  • L212:20: Parameter 'value' implicitly has an 'any' type.
  • L232:18: Parameter 'key' implicitly has an 'any' type.
  • L232:23: Parameter 'value' implicitly has an 'any' type.
src/lib/components/workflow/pending-activity/pending-activity-card.svelte (8)
  • L175:8: Type 'IPayloads | null | undefined' is not assignable to type 'object | IPayloads | IPayload'.
  • L230:20: Parameter 'timeDifference' implicitly has an 'any' type.
  • L247:8: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number | null'.
  • L252:54: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'.
  • L31:26: 'activity.attempt' is possibly 'null' or 'undefined'.
  • L95:12: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'.
  • L97:14: Argument of type 'Duration | null' is not assignable to parameter of type 'string | Duration'.
  • L140:13: 'totalPending' is possibly 'undefined'.
src/lib/components/workflow/pending-nexus-operation/pending-nexus-operation-card.svelte (2)
  • L128:20: Parameter 'timeDifference' implicitly has an 'any' type.
  • L15:26: 'operation.attempt' is possibly 'null' or 'undefined'.
src/lib/components/standalone-activities/activity-input-and-outcome.svelte (1)
  • L20:6: Type 'IPayloads | undefined' is not assignable to type 'object | IPayloads | IPayload'.
src/lib/components/workflow/pending-activities.svelte (7)
  • L33:52: Argument of type 'string | null | undefined' is not assignable to parameter of type 'string'.
  • L37:5: 'pendingActivities' is possibly 'undefined'.
  • L44:29: 'pendingActivities' is possibly 'undefined'.
  • L56:27: 'pendingActivity.attempt' is possibly 'null' or 'undefined'.
  • L95:24: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number | null'.
  • L121:22: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'.
  • L123:24: Argument of type 'Duration | null' is not assignable to parameter of type 'string | Duration'.
src/lib/components/workflow/workflow-callback.svelte (3)
  • L49:23: Parameter 'view' implicitly has an 'any' type.
  • L40:11: Type 'null' cannot be used as an index type.
  • L40:11: Type 'undefined' cannot be used as an index type.
src/lib/holocene/code-block.stories.svelte (10)
  • L84:44: Argument of type 'null' is not assignable to parameter of type '((key: string, value: { hello: string; }) => { hello: string; }) | undefined'.
  • L112:36: Argument of type 'null' is not assignable to parameter of type '((key: string, value: Record<string, unknown>) => Record<string, unknown>) | undefined'.
  • L133:53: Argument of type 'null' is not assignable to parameter of type '((key: string, value: { hello: string; }) => { hello: string; }) | undefined'.
  • L149:53: Argument of type 'null' is not assignable to parameter of type '((key: string, value: { hello: string; }) => { hello: string; }) | undefined'.
  • L157:53: Argument of type 'null' is not assignable to parameter of type '((key: string, value: { hello: string; }) => { hello: string; }) | undefined'.
  • L167:53: Argument of type 'null' is not assignable to parameter of type '((key: string, value: { hello: string; }) => { hello: string; }) | undefined'.
  • L177:6: Argument of type 'null' is not assignable to parameter of type '((key: string, value: TypedPropertyDescriptor[] & { [x: string]: PropertyDescriptor; }) => TypedPropertyDescriptor[] & { ...; }) | undefined'.
  • L189:6: Argument of type 'null' is not assignable to parameter of type '((key: string, value: TypedPropertyDescriptor[] & { [x: string]: PropertyDescriptor; }) => TypedPropertyDescriptor[] & { ...; }) | undefined'.
  • L345:8: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ 'File A': string; 'File B': string; }'.
  • L346:8: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ 'File A': string; 'File B': string; }'.
src/lib/pages/standalone-activity-search-attributes.svelte (1)
  • L11:6: Type 'Record<string, IPayload> | undefined' is not assignable to type 'object | IPayloads | IPayload'.
src/lib/pages/workflow-call-stack.svelte (3)
  • L26:6: Type 'WorkflowExecution | null' is not assignable to type 'Eventual<{ id: string; runId: string; }>'.
  • L45:30: 'workers.pollers.length' is possibly 'undefined'.
  • L109:12: Type 'string | undefined' is not assignable to type 'string | number | boolean'.
src/lib/pages/workflow-metadata.svelte (1)
  • L15:8: Type 'Record<string, string> | undefined' is not assignable to type 'object | IPayloads | IPayload'.
src/lib/pages/workflow-query.svelte (9)
  • L49:5: Type 'string | undefined' is not assignable to type 'string'.
  • L58:32: 'a.name' is possibly 'undefined'.
  • L59:32: 'b.name' is possibly 'undefined'.
  • L64:13: 'a.name' is possibly 'undefined'.
  • L64:34: No overload matches this call.
  • L96:6: Type 'Promise<IPayload[]> | Promise' is not assignable to type 'Promise'.
  • L113:6: Type '{ payloads: IPayloads; } | null' is not assignable to type 'IPayloads | undefined'.
  • L155:12: Type '"retry" | 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" | ... 146 more ... | undefined'.
  • L184:12: Type 'false | "opacity-50"' is not assignable to type 'string | undefined'.
src/lib/pages/workflow-search-attributes.svelte (1)
  • L14:6: Type 'Record<string, string> | undefined' is not assignable to type 'object | IPayloads | IPayload'.
src/routes/(app)/namespaces/[namespace]/archival/+page.svelte (1)
  • L17:23: Property 'name' does not exist on type 'INamespaceInfo | null | undefined'.

Generated by 🚫 dangerJS against 6a927b6

label="Code example"
tabs={['File A', 'File B']}
bind:activeTab
content={hidden

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ 'File A': string; 'File B': string; }'.

@rosanusi rosanusi marked this pull request as ready for review June 15, 2026 16:45
@rosanusi rosanusi requested a review from a team as a code owner June 15, 2026 16:45
@ardiewen ardiewen changed the title [a11y] WCAG 4.1.2 — require label on CodeBlock primitive; add aria-readonly for read-only mode a11y(4.1.2): require label on CodeBlock primitive; add aria-readonly for read-only mode Jul 6, 2026
@github-actions github-actions Bot added a11y Accessibility audit PR a11y:bucket-3 Bucket 3: engineer required a11y:sc-4.1.2 labels Jul 6, 2026
rosanusi and others added 3 commits July 13, 2026 12:50
… read-only mode

Makes the `label` prop required on the CodeBlock primitive so every
CodeMirror instance gets an accessible name via `aria-label` on the
`.cm-content` element. Adds `aria-readonly="true"` when `editable=false`
so AT correctly conveys non-editability (JAWS forms-mode fix). Adds a
dev-time `onMount` guard that throws if label is empty after mount.

Migrates all ~28 affected files: primitive, 4 domain wrappers
(PayloadCodeBlock, PayloadInput, InputAndResultsPayload,
PayloadInputWithEncoding), and ~22 direct consumers across Workflow
History, Queries, Schedules, Activities, Events, Workers, and Archival.

Also fixes the PayloadInput orphaned <Label for> association (§NEW1):
replaces <Label for={id}> with <span> and forwards label to the inner
CodeBlock so the accessible name is set on the cm-content element rather
than a wrapper div that for= can't reach.

New i18n keys: common.json, events.import-format-{1,2},
standalone-activities.{activity-input,activity-outcome,cluster-config,
namespace-config}, workers.{heartbeat-config,setup-guide-command},
workflows.{query-result,callback-metadata,update-input,update-result}.

A11y-Audit-Ref: 4.1.2-codemirror-no-name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
code-block.stories.svelte: add label to "With Header" and "Test page
scrolling" stories which were missing the now-required prop.

compute-fields.svelte: add label to the Terraform IAM template CodeBlock
added in the serverless worker form refactor (landed in main after the
original PR was cut).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Follows the reinforcement guidance from #3556: bespoke onMount
accessible-name checks are removed in favor of the axe suite (DT-4255),
which computes the real accessible name. The dev throw also broke
Storybook/tests and could crash consumer dev environments.

Revert `label` to optional (`label?: string`, default '') so this is
not a breaking change for downstream consumers — cloud-ui has ~74
CodeBlock usages, none passing `label`, which a required prop would
break on tarball repack. The ui-side labels remain; axe enforces names.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ardiewen ardiewen force-pushed the wcag/4.1.2-codemirror-no-name branch from 8150445 to 74de435 Compare July 13, 2026 16:52
@ardiewen

ardiewen commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Rebased onto main (was 91 behind; conflicts resolved — note two schedule files were deleted upstream, so their label edits dropped).

Changes from review:

  • Removed the onMount throw; kept label optional — per a11y(4.1.2): add label prop to Button primitive and enforce accessible names #3556, enforcement belongs in the axe suite (DT-4255). A required prop / throw would break cloud-ui's ~74 label-less CodeBlock sites on repack.
  • Made read-only blocks focusable (tabindex="0") — the name/aria-readonly were in the AX tree, but .cm-content (contenteditable=false) wasn't focusable, so SRs skipped it (only the copy button was reachable). Each read-only block is now a tab stop announcing "…, read only". Note an accessible-name axe check passes without this — it doesn't test reachability.

pnpm check green; verified in-browser.

Read-only CodeMirror sets .cm-content to contenteditable="false" with no
tabindex, so the element is not focusable — the aria-label/aria-readonly
land in the AX tree but no keyboard/SR user can reach the region, and it
is never announced (only the copy button was focusable).

Add tabindex="0" to the read-only contentAttributes so the named,
read-only textbox is reachable and announced ("Input, read only").
Verified in-browser: focusable + named. Note: an accessible-name axe
check passes without this, since it does not test reachability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ardiewen ardiewen merged commit ba18b43 into main Jul 13, 2026
18 checks passed
@ardiewen ardiewen deleted the wcag/4.1.2-codemirror-no-name branch July 13, 2026 18:45
rossedfort added a commit that referenced this pull request Jul 13, 2026
Auto-generated version bump from 2.52.0 to 2.52.1

Bump type: patch

Changes included:
- [`3be0df00`](3be0df0) fix(schedules): default absent second/minute/hour to 0 when editing (#3653)
- [`508b65b7`](508b65b) fix(schedules): default emptied second/minute/hour to 0 on submit (#3655)
- [`1f0e149c`](1f0e149) fix(workers): reject $LATEST qualifier in Lambda ARN validation (#3657)
- [`b42b0776`](b42b077) a11y(2.4.3): inert-based focus containment + restore; drop trap from native dialogs (#3598)
- [`cc491a29`](cc491a2) a11y(2.4.3): keep toast live region announcing during a focus trap [DT-4252] (#3654)
- [`ddc7f8e2`](ddc7f8e) a11y(2.4.11): add scroll-padding so sticky overlays don't obscure focused elements (#3532)
- [`b7e3a472`](b7e3a47) FilterBar icon update (#3660)
- [`09318ec6`](09318ec) Add danger PR comment for api version bump (#3662)
- [`273cc4be`](273cc4b) a11y(4.1.2): add label prop to Button primitive and enforce accessible names (#3556)
- [`4507115f`](4507115) Use an editable combobox for the Cloud Run region field (DT-4232) (#3636)
- [`694a7471`](694a747) a11y(1.4.13): migrate saved-query nav tooltips to Tooltip primitive (#3607)
- [`8b384a40`](8b384a4) a11y(4.1.2): fix nested interactive elements in Copyable component (#3558)
- [`1bfd2750`](1bfd275) fix describe SANO API requests to include runId parameter (#3661)
- [`ba18b43e`](ba18b43) a11y(4.1.2): require label on CodeBlock primitive; add aria-readonly for read-only mode (#3557)
- [`6e2290f1`](6e2290f) feat(deployments): make compute provider picker configurable (#3659)
- [`4470b76a`](4470b76) fix(workers): persist WDV compute-config edits via update mask; revert $LATEST validation (#3658)
- [`17fd591b`](17fd591) fix(nexus-operations): make start standalone nexus operation timeouts optional (#3656)

Co-authored-by: rossedfort <11775628+rossedfort@users.noreply.github.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.2 a11y Accessibility audit PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants