fix(plugin-detail,i18n): announce record:path stage state in the accessible name - #5958
Merged
Merged
Conversation
…ssible name record:path conveyed travelled / upcoming / lost-terminal stages with colour plus a glyph, and both glyphs are aria-hidden decoration. aria-current="step" marked the current stage and nothing else, so a screen-reader user heard a run of identically-announced items, and a rejected stage announced exactly like an ordinary stage the record had not reached yet. WCAG 2.2 SC 1.4.1. Each stage now carries an accessible name composing its already picklist-localized label with its state, from five new detail.pathStage* keys translated in all ten locale packs and mirrored byte-identically into DETAIL_DEFAULT_TRANSLATIONS for the provider-less path. The name is composed into aria-label rather than visually-hidden text because listitem takes its name from the author only: measured on this tree, sr-only text inside a role="listitem" computes to an EMPTY accessible name, so that shape would have satisfied a DOM assertion while delivering nothing to the accessibility tree. The glyphs stay decorative and the #5768 readout contract (role, aria-current, data-stage-* instruments) is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZyKZejBWZoCSj1NP35wcp
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5916
record:pathdistinguished travelled, upcoming and lost-terminal stages with colour plus a✓/✗glyph, and both glyphs arearia-hiddendecoration.aria-current="step"marked the current stage and nothing else, so a screen-reader user heard a run of identically-announced items — and alostterminal announced exactly like an ordinary stage the record had not reached yet. WCAG 2.2 SC 1.4.1 Use of Color.Premise check (the card was filed from inside a rewrite of this file)
The card was written while #5917 (
render record:path as a readout, not a fake segmented control, closing #5768) was in flight, and its JSX snippet is not current source. Re-read against the landed file at0b12a3315, the defect stands unchanged:✓isaria-hiddenrecord-path.tsx:177✗isaria-hiddenrecord-path.tsx:175aria-current="step"is the only programmatic signalrecord-path.tsx:170#5917 did add
data-stage-state/data-stage-terminal, but its own header scopes them to test assertability — adata-*attribute is not in the accessibility tree. That header now says so explicitly.The shape, and why this one
The card offered two shapes and picked neither: visually-hidden per-stage text, or an
aria-labelcomposing label + state. The choice is a measurement, not a preference.listitemis not a name-from-contents role, so text placed inside a stage leaves the accessible name empty. Measured on this tree with the suite's own harness before writing the fix (markup described rather than pasted — GitHub's body sanitizer strips bare tags, including inside a fence):role="listitem"wrapping aspan.sr-onlyreadingcompleted,plus the label草稿''— the visually-hidden shaperole="listitem"carryingaria-label="草稿, completed"'草稿, completed'The visually-hidden shape would have satisfied a DOM assertion ("the span is there") while delivering nothing to the accessibility tree — the exact failure mode the card warned against. So:
aria-labelon the listitem, composing the stage's own already picklist-localizedstage.labelwith its state. Because both halves render from that one variable, the announced name cannot drift from what is on screen (asserted as group D). The glyphs stay decorative, per the triage ruling.i18n
Five keys —
detail.pathStage{Completed,Current,Upcoming,LostCurrent,LostUpcoming}— in all ten locale packs, and mirrored byte-identically intoDETAIL_DEFAULT_TRANSLATIONSfor the provider-less path.The ten packs and the defaults map are not scope creep beyond the card's stated surface ("the i18n keys the change needs"); the repo's gates make them mandatory.
all-locales-key-parityfails a keyenhas that any pack lacks and a key a pack has thatenlacks, anddefaults-maps-mirror-en-packfails a map row whose key the pack lacks. Five keys therefore means fifty pack rows plus five map rows, or nothing.Verification
All commands below were run at
f4cb3fec4, the branch head.Tests —
pnpm exec vitest runfrom the repo root (this repo's guard refuses the per-packagepnpm --filter … testform):packages/plugin-detail(whole package)packages/i18n/src/__tests__/+app-shell/…/defaults-maps-mirror-en-pack.test.tsxrecord:path(console ×3, app-shell previews ×1)Gates — verdict lines as each gate printed them, exit codes captured before any pipe:
check:control-bytes—✅ check-control-bytes: OK (scanned 4942 tracked text file(s); skipped 85 binary).check:i18n-keys—Every in-scope call-site key resolves against the en pack (2934 keys), every literal inline defaultValue matches the value the pack serves, every call site passes exactly the arguments that value has holes for…(this is what confirms{ stage }matches the{{stage}}hole in all five keys)check:i18n-drift—No en value changed in this range./5 key(s) added, 0 removed — those are all-locales-key-parity'scheck-changeset-presence.mjs—✅ 14 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)turbo run type-check --filter=@object-ui/plugin-detail --filter=@object-ui/i18n— 14 tasks successful (plugin-detail runstsc --noEmitfollowed bytsc -p tsconfig.test.json, so the new suites are type-checked too)turbo run lintfor both packages — 3 tasks successful, 0 errors.record-path.tsxreports 15 warnings; the file at the merge-base reports the same 15, so this change adds none.Ablation — the new tests bite, and only where they should. Deleting the single
aria-labelline fromrenderStage, then re-running the two new suites plus the pre-existingrecord-path.inertReadout.test.tsx:15 red / 12 green, matching the direction predicted in both suite headers before running. Red: every case whose subject is the accessible name — group B (6), the three name-bearing cases of group C, group D (1), and all five locale cases. Green: group A (the #5768 readout contract —
aria-current, the decorative glyphs, the labels on screen), group C's "the ✗ staysaria-hidden" case, and the whole pre-existinginertReadoutsuite, none of which ever depended on the name.The mutation was confirmed on disk by grepping for the deleted text (1 → 0) rather than by the editor's exit code, and the script carried
trap … EXIT INT TERM; the restore was verified byte-identical withdiff -qagainst a pre-ablation copy, and the marker count returned to 1. No rebuild was needed and none is claimed: both suites import the subject relatively (../record-path) and resolve cross-package imports to each package's ownsrcthrough the rootvitest.config.mtsalias table, so nothing resolves through adist/— the propertyrecord-path.inertReadout.test.tsxalready records for itself.Not in this PR
divand so names nothing. An i18n defect rather than this card's colour-alone defect, and thedivneeds a decision, not a string swap.wonterminus with colour alone — after #5916 it still announces as a plain upcoming stage #5957: an unreachedwonterminus is still distinguished by hue alone and announces as a plain upcoming stage. Same WCAG class, but the fix is new user-facing copy in ten packs rather than a mechanical change, so it failed the bounded-in-place bar.Generated by Claude Code