Repository navigation
fix(plugin-view,console,layout,components,fields): name and nest the controls axe flagged on the list and settings pages (objectui#11690) - #11732
Conversation
…controls axe flagged on the list and settings pages (objectui#11690) - ViewTabBar: the add button is named through `view.addView`; views are buttons (aria-current on the current one) and the actions button is the view button's sibling, not its child; the rename box is named. - SettingsField: each row's label is bound to its control (htmlFor, and aria-labelledby for the group and colour-hex controls). - NavigationRenderer: the sortable node is the row's own li, and a separator or nested group inside a menu roots at an li. - data-table: the selection checkboxes are named through the existing table.selectAllRows / table.selectRow keys. - PercentCellRenderer: the progress bar is named by its value span. Each surface is pinned by an axe run (wcag2a + wcag2aa, color-contrast off) that also asserts the rules really ran. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
ℹ️ Console Performance Budget — not measuredThis run did not produce a console bundle to measure, so there is no pass/fail verdict for the performance budget. This is not a budget violation. Nothing was measured — the numbers a real violation would carry are simply absent.
See the workflow run for details. 📦 Bundle Size Report
Size Limits
|
…in (objectui#11690) The console compiles its own tests (`include: ["src"]`, `noUnusedLocals`, `jsx: react-jsx`), so the unread default import failed `tsc` in the console build with TS6133. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Contract reviewServed-tier: Inputs read: card objectui#11690 (body and its four comments: claim 6018454300, report 6020678071, claim amendment 6020771085, round-2 report 6020967800), PR objectui#11732 (body, 12-file list, its two bot comments, the net diff against ① Derived judgmentsAccept set. Nothing an author writes changes: no schema, prop, config or spec key is added, widened or removed. Every input type the diff touches ( Public surface. What consumers can observe changes in five rendered outputs:
Card pins. "Each named control gets an accessible name, through translation keys": met (the one deviation above is better than the letter). "Settings labels bind to their controls, and the tab bar's roles nest validly": met; the roles nest validly by not being tab roles, which is the seat's ruling A. "axe on the two pages reports none of the listed rules": NOT MEASURED at page level by the dev (no backend in the container); substituted by component pins at each traced producer, each with a rule-ran control, and the card's node counts map onto the producers: showcase Reverse verification. The dev's base-restore table (each pin red on Attribution and governance. PR body, changeset and comments carry session ids only, no model identifier. No governed surface is in the file list (Governed Surface Queue Guard: success). ② Semver levelChangeset Level against the diff: the diff publishes no new export, prop, type member or language-pack key, so nothing demands
③ Boundary flagsDev open_questions (report 6020678071).
Dev deviations.
NOT MEASURED (dev-flagged). Page-level axe on Out-of-scope findings (dev; carrier none, noted, not filed). English literals still in Reviewer's own escalation (not a dev flag). The reorderable arm's Enter-or-Space-starts-a-drag behaviour named in ① item 1: pre-existing on Implemented-by: VERDICT: PASS Generated by Claude Code |
…ctui#11690) Measured with a throwaway user-event probe (not committed): with the bar not reorderable, Enter and Space switch to the focused view button; with drag-to-reorder on, dnd-kit's KeyboardSensor prevents both keydowns and starts a keyboard drag, so no switch, while a click still switches. The base ViewTabBar behaves the same in both arms. The changeset sentence claimed Enter or Space switches in every case. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
…i.waitFor (objectui#11690)
`getLazyIcon('Building2')` is lucide's DynamicIcon, a forwardRef that sets
its icon data from a promise its mount effect starts. `vi.waitFor` leaves
IS_REACT_ACT_ENVIRONMENT on, so when that update landed after
`findByText` restored the flag, React logged "An update to ForwardRef
inside a test was not wrapped in act(...)" through the console.error the
test holds silent, and the assertion that nothing is logged failed on some
runs. RTL's waitFor keeps the flag off while it waits, as findByText does.
iconNoise() and its assertion are unchanged.
Pre-existing on main: an in-process harness repeating the test's sequence
logged the ForwardRef warning on main 6be0f7a as on this branch, and never
with RTL's waitFor.
Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Contract reviewServed-tier: Inputs read: card objectui#11690 (body and its six comments: claim 6018454300, report 6020678071, claim amendment 6020771085, round-2 report 6020967800, round-3 report 6021264463, round-4 report 6023574940), PR objectui#11732 (body, 13-file list, its five comments, among them the Since the record on ① Derived judgmentsAccept set. Nothing an author writes changes. No schema, spec key, prop, config field or exported type is added, widened or removed: Public surface. Five rendered outputs change, plus one test outside the card; each judged:
Card pins. "Each named control gets an accessible name, through translation keys": met, with item 5's deliberate departure. "Settings labels bind to their controls, and the tab bar's roles nest validly": met; the roles nest validly by not being tab roles, the seat's ruling A. "axe on the two pages reports none of the listed rules": NOT MEASURED at page level by the dev (no backend in the container), substituted by component pins at each traced producer, each with a rule-ran control; the card's counts map onto the producers: showcase Reverse verification. The dev's base-restore table (each pin red on Attribution and governance. Commit trailer pairs are model-free ( ② Semver levelChangeset Level against the diff: no export, prop, type member or language-pack key is published, so nothing demands
③ Boundary flagsDev Dev
NOT MEASURED (dev-flagged). Page-level axe on Dev Reviewer's own notes (not dev flags). The two nits in ① item 1 (the stale Implemented-by: VERDICT: PASS Generated by Claude Code |
…asured (objectui#11690) Page-level axe was never re-run on the two pages (no backend in the container), so the release text no longer says the pages report none of the faults now. It says what was measured: each fault is fixed where it is produced and pinned by an axe run on that component. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Contract reviewServed-tier: Inputs read: card objectui#11690 (body and its seven comments: claim 6018454300, report 6020678071, claim amendment 6020771085, reports 6020967800, 6021264463, 6023574940 and 6023838506), PR objectui#11732 (body, 13-file list, its seven comments, among them the Since the record on ① Derived judgmentsAccept set. Nothing an author writes changes. No schema, spec key, prop, config field or exported type is added, widened or removed: Public surface. Five rendered outputs change, plus one test outside the card and the release text; each judged:
Card pins. "Each named control gets an accessible name, through translation keys": met, with item 5's deliberate departure. "Settings labels bind to their controls, and the tab bar's roles nest validly": met; the roles nest validly by not being tab roles, the seat's ruling A. "axe on the two pages reports none of the listed rules": NOT MEASURED at page level by the dev, substituted by component pins at each traced producer, each with a rule-ran control; the card's counts map onto the producers: showcase Reverse verification. The dev's base-restore table (each pin red on Attribution and governance. The five commits end in the model-free trailer pair; the PR body carries the session-URL footer; the diff, the changeset and the comments carry no model identifier. No governed surface is in the 13-file list (Governed Surface Queue Guard: success). Line Citation Gate: success. ② Semver levelChangeset Level against the diff: no export, prop, type member or language-pack key is published, so nothing demands
③ Boundary flagsDev Dev
NOT MEASURED (dev-flagged). Page-level axe on Dev Reviewer's own escalation (not a dev flag). Three nits have now stood in three records across three patch commits without a carrier: the stale Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #11690
Clause-②: yes
Screen readers can name and reach the controls axe flagged on the showcase task list and on Setup → Settings → Authentication. Each traced producer is pinned with axe-core at component level. Implemented by session
https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8(seatdomain:ui#3, claim comment 6018454300).What changed, by producer
button-name(showcase, 1 of 12):view-tab-addpackages/plugin-view/src/ViewTabBar.tsxaria-labeland tooltip read the existingview.addViewkey (the tooltip was a hard-coded "Add View")aria-required-parentx4,nested-interactivex1ViewTabBar.tsx: each view wasrole="tab"with no tablist, and the active view's actions button sat inside its tabbutton(the current one hasaria-current="true"). The actions button is now its sibling inside a role-less frame. The rename box now sits outside the button and is named viaview.renamebutton-name(showcase, 11 of 12)packages/components/src/renderers/complex/data-table.tsx: select-all and per-row Radix checkboxes (abuttonwith role checkbox), unnamedtable.selectAllRows/table.selectRowkeys; en defaults added toTABLE_DEFAULT_TRANSLATIONSaria-progressbar-namex10packages/fields/src/index.tsx,PercentCellRendereronlyaria-labelledbyits formatted value span, so the name is in the viewer's localelabelx15,button-namex9 (settings)apps/console/src/pages/settings/SettingsField.tsx: the row'sLabelhad nohtmlForhtmlFor(inputs, textarea, switch, select trigger, domain combobox, colour). The radio group, multiselect group and colour hex box usearia-labelledbylist/listitemx50 (settings)packages/layout/src/NavigationRenderer.tsx: with drag-to-reorder on (the desktop default), each sortable row was adivbetween the menuuland itslili. A separator (anaria-hiddenitem) and a nested group inside a menu now root at anlitooWhy buttons and not a fixed tablist. A
tab's children are presentational, so a focusable button inside it isnested-interactive. Atablistmay own only tabs, so the same button beside a tab isaria-required-children. Under tab roles the per-view actions button could be neither reachable nor valid. These views also never had the tabs keyboard model (arrow keys, a tab panel): every view was its own Tab stop, which is a button. Enter or Space switches to it when the bar is not reorderable, as the console renders it. With drag-to-reorder on, both keys start dnd-kit's keyboard drag instead, measured the same before and after this PR. Keyboard reach is unchanged: view, then its actions button, then the next view. The layout keeps its measure (ml-2 mr-3on the moved trigger equals the old gap plusml-0.5plus the tab's right padding). The changeset marks therole="tab"/aria-selectedremoval as breaking for anything that queried by them.Where the producer was outside the expected surface. The dispatch allowed a fix at the producer when it was measured elsewhere. That happened twice.
NavigationRenderer.tsx: the claim's surface covers list nesting "wherever on the settings page".data-table.tsx: the 11 other unnamed buttons on the task list. Attributing those 11 to the data table is an inference, not a page reading. A component axe run ondata-tablereproducesbutton-nameon exactly the select-all plus one-per-row checkboxes. The showcase task list renders 10 rows (the card'saria-progressbar-namex10), and 1 + 10 = 11.Pins and the reverse verification
Five new pins. Each runs axe-core 4.13 with tags wcag2a + wcag2aa (
color-contrastoff, because it needs layout happy-dom does not compute and is out of this card). Each asserts an empty violation list AND that the relevant rules appear inpasses, so a zero there is a reading:packages/plugin-view/src/__tests__/ViewTabBar.a11y-11690.test.tsx: static and drag-reorderable bars, rename state, keyboard-reach checks, add-button name read in zh against the packapps/console/src/pages/settings/__tests__/SettingsField.labelBinding-11690.test.tsx: every input type; each control's accessible name equals its own row labelpackages/fields/src/__tests__/PercentCellRenderer.barName-11690.test.tsx: name equals the shown value in de (1.235 %)packages/layout/src/__tests__/NavigationRenderer.listNesting-11690.test.tsx: grouped and group-free menus, reorder on and off, with separators and a nested group; everyulchild is anlipackages/components/src/__tests__/data-table-selection-names-11690.test.tsx: names read in de against the packReverse verification, from the committed fix
1ebcf6d. Each source file was put back to base7300fca, its pin run, then restored withgit checkout HEAD. The restore was proven by the HEAD blob hash; finalgit diff HEADwas empty:ViewTabBar.tsxaria-required-parent,button-name,nested-interactive,labelSettingsField.tsxbutton-name,labelfields/src/index.tsxaria-progressbar-nameNavigationRenderer.tsxlichildrendata-table.tsxbutton-nameOne existing pin was re-pointed:
NavigationRenderer.groupedReorder-11626.test.tsxcounted the sortable wrappers as[data-sidebar="menu"] > div. That is exactly the defect. It now counts> liwith the same intent (one sortable row per drawn entry, none focusable) and also asserts no non-lichild.Verification (head
1ebcf6d; console compile and settings suite re-run onc8699a0andaaa5bfc)pnpm exec vitest run --maxWorkers=2at the repo root:packages/plugin-view/66 files / 615 tests passed;packages/layout/39 / 348;packages/fields/235 passed, 1 skipped / 3670 tests;packages/components/362 passed, 1 skipped / 3681 tests;apps/console/src/pages/settings/10 / 80ObjectView.*tests 39 / 332; every app-shell and console test namingUnifiedSidebarorNavigationRenderer22 / 167; plugin-grid and plugin-list tests driving checkboxes by role 12 / 101; all greenpnpm --filter PKG type-checkexit 0 for plugin-view, layout, fields and components (script echoedtsc --noEmit && tsc -p tsconfig.test.json). The dependency closure was built first. Each new pin was confirmed inside its package's test program with--listFilesOnlypnpm exec eslinton the 11 touched files: 0 errors. Warning counts on the 5 source files equal the base'scheck:control-bytes,check:test-path-roots,check:phantom-deps,check:changeset-claims,check:pending-changeset-literals,check:new-line-citations,check:i18n-keys,check:i18n-drift,check:i18n-dead-keys,check:i18n-designer-parity, pluscheck-changeset-presence/-no-major/-fixed/-overwrite: all exit 0pnpm --filter @object-ui/console type-checkexit 0 onc8699a0, against the built 34-package closure. Round 1 (1ebcf6d) failed the console build with TS6133 on an unusedReactimport in the new settings pin;c8699a0removes it. A planted leg (the pin at1ebcf6d) reproduces exactly that one error.SettingsIcons.knownName-11679.test.tsx(the objectui#11679 pin besideSettingsField.tsx) failed shard 7 onec3561dwith an act() warning namingForwardRef. The lucideDynamicIconsets its icon data from a promise, andvi.waitForleftIS_REACT_ACT_ENVIRONMENTon, so the update was an act violation whenever it landed afterfindByTextrestored the flag. This is pre-existing on main: a throwaway in-process harness repeating the test sequence logged it on6be0f7a(2 of 550) as on this branch (5 of 550), and never with the RTLwaitFor(0 of 550 on both).aaa5bfcwaits with the RTLwaitFor;iconNoise()and its assertion are unchanged. Onaaa5bfc: the isolated test passed 40 of 40 under 4 CPU burners and the whole file 10 of 10; console type-check exit 0; settings suite 10 files / 80 tests.origin/mainhas moved since base (7a2c60b, objectui#11725, also editsdata-table.tsx, in a different region).git merge-treeagainst current main is clean.Clause-② note
The claim declared
Clause-②: yesbecause it expected new language-pack keys. The diff adds none: every new name reads a key the packs already carried (view.addView,view.rename,table.selectAllRows,table.selectRow). No export, prop or type member changes either. What does change publicly is the tab bar's DOM:role="tab"/aria-selectedbecomebutton/aria-current, and the changeset says so as breaking underminor. The seat can judge whetheryesstill applies.Acceptance notes (observed, not filed, no carrier)
ViewTabBarstill renders English literals for "Unsaved changes", "Save as View", "Reset", the "Active filters" / "Active sort" tooltip, andaria-roledescription"view tab" (drag mode). The packs already haveview.unsavedChanges,view.saveAsView,view.activeFilters,view.activeSort.data-tablerow actions carryaria-label"Row actions" in English. All are named, so none is in this card's rules.sliderrow: the Radix thumb has no accessible name (the shadcn primitive is a no-touch zone). The authentication page has no slider, so it was not measured on a page.check:i18n-dead-keys(report-only) listsview.addViewandview.renameas needs-review. It cannot see call sites made through the file-localviewTabLabelhelper.axe-coreandvitest-axeare root devDependencies, but no test in the tree ran axe before this PR. The cited app-shell hit is the word "relaxed".Related: objectui#11689 (seat 2) edits
BooleanCellRendererinpackages/fields/src/index.tsx. This PR touches only thePercentCellRendererregion there. The later lander merges.Generated by Claude Code